Skip to content

Commit d01bf55

Browse files
authored
refactor!: useStore & useVueImportMap composable (#207)
1 parent dbe1b40 commit d01bf55

15 files changed

+554
-544
lines changed

src/Repl.vue

+5-23
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<script setup lang="ts">
22
import SplitPane from './SplitPane.vue'
33
import Output from './output/Output.vue'
4-
import { ReplStore, type SFCOptions, type Store } from './store'
5-
import { computed, provide, ref, toRef, watchEffect } from 'vue'
6-
import type { EditorComponentType } from './types'
4+
import { type Store, useStore } from './store'
5+
import { computed, provide, ref, toRef } from 'vue'
6+
import { type EditorComponentType, injectKeyStore } from './types'
77
import EditorContainer from './editor/EditorContainer.vue'
88
99
export interface Props {
@@ -15,7 +15,6 @@ export interface Props {
1515
showImportMap?: boolean
1616
showTsConfig?: boolean
1717
clearConsole?: boolean
18-
sfcOptions?: SFCOptions
1918
layout?: 'horizontal' | 'vertical'
2019
layoutReverse?: boolean
2120
ssr?: boolean
@@ -32,7 +31,7 @@ export interface Props {
3231
3332
const props = withDefaults(defineProps<Props>(), {
3433
theme: 'light',
35-
store: () => new ReplStore(),
34+
store: () => useStore(),
3635
autoResize: true,
3736
showCompileOutput: true,
3837
showImportMap: true,
@@ -49,7 +48,6 @@ const props = withDefaults(defineProps<Props>(), {
4948
useCode: '',
5049
},
5150
}),
52-
sfcOptions: () => ({}),
5351
layout: 'horizontal',
5452
})
5553
@@ -59,28 +57,12 @@ if (!props.editor) {
5957
6058
const outputRef = ref<InstanceType<typeof Output>>()
6159
62-
watchEffect(() => {
63-
const { store } = props
64-
const sfcOptions = (store.options = props.sfcOptions || {})
65-
sfcOptions.script ||= {}
66-
sfcOptions.script.fs = {
67-
fileExists(file: string) {
68-
if (file.startsWith('/')) file = file.slice(1)
69-
return !!store.state.files[file]
70-
},
71-
readFile(file: string) {
72-
if (file.startsWith('/')) file = file.slice(1)
73-
return store.state.files[file].code
74-
},
75-
}
76-
})
77-
7860
props.store.init()
7961
8062
const editorSlotName = computed(() => (props.layoutReverse ? 'right' : 'left'))
8163
const outputSlotName = computed(() => (props.layoutReverse ? 'left' : 'right'))
8264
83-
provide('store', props.store)
65+
provide(injectKeyStore, props.store)
8466
provide('autoresize', props.autoResize)
8567
provide('import-map', toRef(props, 'showImportMap'))
8668
provide('tsconfig', toRef(props, 'showTsConfig'))

src/SplitPane.vue

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
<script setup lang="ts">
2-
import { computed, inject, reactive, ref } from 'vue'
3-
import type { Store } from './store'
2+
import { computed, inject, reactive, ref, toRef } from 'vue'
3+
import { injectKeyStore } from './types'
44
55
const props = defineProps<{ layout?: 'horizontal' | 'vertical' }>()
66
const isVertical = computed(() => props.layout === 'vertical')
77
88
const container = ref()
99
1010
// mobile only
11-
const store = inject('store') as Store
12-
const showOutput = ref(store.initialShowOutput)
11+
const store = inject(injectKeyStore)!
12+
const showOutput = toRef(store, 'showOutput')
1313
1414
const state = reactive({
1515
dragging: false,

src/editor/EditorContainer.vue

+6-7
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,20 @@ import FileSelector from './FileSelector.vue'
33
import Message from '../Message.vue'
44
import { debounce } from '../utils'
55
import { inject, ref, watch } from 'vue'
6-
import type { Store } from '../store'
76
import MessageToggle from './MessageToggle.vue'
8-
import type { EditorComponentType } from '../types'
7+
import { type EditorComponentType, injectKeyStore } from '../types'
98
109
const SHOW_ERROR_KEY = 'repl_show_error'
1110
1211
const props = defineProps<{
1312
editorComponent: EditorComponentType
1413
}>()
1514
16-
const store = inject('store') as Store
15+
const store = inject(injectKeyStore)!
1716
const showMessage = ref(getItem())
1817
1918
const onChange = debounce((code: string) => {
20-
store.state.activeFile.code = code
19+
store.activeFile.code = code
2120
}, 250)
2221
2322
function setItem() {
@@ -38,11 +37,11 @@ watch(showMessage, () => {
3837
<FileSelector />
3938
<div class="editor-container">
4039
<props.editorComponent
41-
:value="store.state.activeFile.code"
42-
:filename="store.state.activeFile.filename"
40+
:value="store.activeFile.code"
41+
:filename="store.activeFile.filename"
4342
@change="onChange"
4443
/>
45-
<Message v-show="showMessage" :err="store.state.errors[0]" />
44+
<Message v-show="showMessage" :err="store.errors[0]" />
4645
<MessageToggle v-model="showMessage" />
4746
</div>
4847
</template>

src/editor/FileSelector.vue

+13-17
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
<script setup lang="ts">
2-
import {
3-
type Store,
4-
importMapFile,
5-
stripSrcPrefix,
6-
tsconfigFile,
7-
} from '../store'
2+
import { injectKeyStore } from '../../src/types'
3+
import { importMapFile, stripSrcPrefix, tsconfigFile } from '../store'
84
import { type Ref, type VNode, computed, inject, ref } from 'vue'
95
10-
const store = inject('store') as Store
6+
const store = inject(injectKeyStore)!
117
128
/**
139
* When `true`: indicates adding a new file
@@ -23,7 +19,7 @@ const pendingFilename = ref('Comp.vue')
2319
const showTsConfig = inject<Ref<boolean>>('tsconfig')
2420
const showImportMap = inject<Ref<boolean>>('import-map')
2521
const files = computed(() =>
26-
Object.entries(store.state.files)
22+
Object.entries(store.files)
2723
.filter(
2824
([name, file]) =>
2925
name !== importMapFile && name !== tsconfigFile && !file.hidden,
@@ -37,7 +33,7 @@ function startAddFile() {
3733
3834
while (true) {
3935
let hasConflict = false
40-
for (const filename in store.state.files) {
36+
for (const filename in store.files) {
4137
if (stripSrcPrefix(filename) === name) {
4238
hasConflict = true
4339
name = `Comp${++i}.vue`
@@ -68,18 +64,18 @@ function doneNameFile() {
6864
const oldFilename = pending.value === true ? '' : pending.value
6965
7066
if (!/\.(vue|js|ts|css|json)$/.test(filename)) {
71-
store.state.errors = [
67+
store.errors = [
7268
`Playground only supports *.vue, *.js, *.ts, *.css, *.json files.`,
7369
]
7470
return
7571
}
7672
77-
if (filename !== oldFilename && filename in store.state.files) {
78-
store.state.errors = [`File "${filename}" already exists.`]
73+
if (filename !== oldFilename && filename in store.files) {
74+
store.errors = [`File "${filename}" already exists.`]
7975
return
8076
}
8177
82-
store.state.errors = []
78+
store.errors = []
8379
cancelNameFile()
8480
8581
if (filename === oldFilename) {
@@ -122,7 +118,7 @@ function horizontalScroll(e: WheelEvent) {
122118
<div
123119
v-if="pending !== file"
124120
class="file"
125-
:class="{ active: store.state.activeFile.filename === file }"
121+
:class="{ active: store.activeFile.filename === file }"
126122
@click="store.setActive(file)"
127123
@dblclick="i > 0 && editFileName(file)"
128124
>
@@ -154,17 +150,17 @@ function horizontalScroll(e: WheelEvent) {
154150

155151
<div class="import-map-wrapper">
156152
<div
157-
v-if="showTsConfig && store.state.files[tsconfigFile]"
153+
v-if="showTsConfig && store.files[tsconfigFile]"
158154
class="file"
159-
:class="{ active: store.state.activeFile.filename === tsconfigFile }"
155+
:class="{ active: store.activeFile.filename === tsconfigFile }"
160156
@click="store.setActive(tsconfigFile)"
161157
>
162158
<span class="label">tsconfig.json</span>
163159
</div>
164160
<div
165161
v-if="showImportMap"
166162
class="file"
167-
:class="{ active: store.state.activeFile.filename === importMapFile }"
163+
:class="{ active: store.activeFile.filename === importMapFile }"
168164
@click="store.setActive(importMapFile)"
169165
>
170166
<span class="label">Import Map</span>

src/import-map.ts

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { computed, version as currentVersion, ref } from 'vue'
2+
3+
export function useVueImportMap(
4+
defaults: {
5+
runtimeDev?: string | (() => string)
6+
runtimeProd?: string | (() => string)
7+
serverRenderer?: string | (() => string)
8+
} = {},
9+
) {
10+
function normalizeDefaults(defaults?: string | (() => string)) {
11+
if (!defaults) return
12+
return typeof defaults === 'string' ? defaults : defaults()
13+
}
14+
15+
const productionMode = ref(false)
16+
const vueVersion = ref<string | undefined>()
17+
const importMap = computed<ImportMap>(() => {
18+
const vue =
19+
(!vueVersion.value &&
20+
normalizeDefaults(
21+
productionMode.value ? defaults.runtimeProd : defaults.runtimeDev,
22+
)) ||
23+
`https://cdn.jsdelivr.net/npm/@vue/runtime-dom@${
24+
vueVersion.value || currentVersion
25+
}/dist/runtime-dom.esm-browser${productionMode.value ? `.prod` : ``}.js`
26+
27+
const serverRenderer =
28+
(!vueVersion.value && normalizeDefaults(defaults.serverRenderer)) ||
29+
`https://cdn.jsdelivr.net/npm/@vue/server-renderer@${
30+
vueVersion.value || currentVersion
31+
}/dist/server-renderer.esm-browser.js`
32+
return {
33+
imports: {
34+
vue,
35+
'vue/server-renderer': serverRenderer,
36+
},
37+
}
38+
})
39+
40+
return {
41+
productionMode,
42+
importMap,
43+
vueVersion,
44+
defaultVersion: currentVersion,
45+
}
46+
}
47+
48+
export interface ImportMap {
49+
imports?: Record<string, string | undefined>
50+
scopes?: Record<string, Record<string, string>>
51+
}
52+
53+
export function mergeImportMap(a: ImportMap, b: ImportMap): ImportMap {
54+
return {
55+
imports: { ...a.imports, ...b.imports },
56+
scopes: { ...a.scopes, ...b.scopes },
57+
}
58+
}

src/index.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
export { default as Repl } from './Repl.vue'
22
export { default as Preview } from './output/Preview.vue'
3-
export { ReplStore, File } from './store'
3+
export {
4+
useStore,
5+
File,
6+
type SFCOptions,
7+
type StoreState,
8+
type Store,
9+
type ReplStore,
10+
} from './store'
11+
export { useVueImportMap, mergeImportMap, type ImportMap } from './import-map'
412
export { compileFile } from './transform'
513
export type { Props as ReplProps } from './Repl.vue'
6-
export type { Store, StoreOptions, SFCOptions, StoreState } from './store'
714
export type { OutputModes } from './types'

src/monaco/Monaco.vue

+4-5
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import {
1313
import * as monaco from 'monaco-editor-core'
1414
import { initMonaco } from './env'
1515
import { getOrCreateModel } from './utils'
16-
import type { Store } from '../store'
17-
import type { EditorMode } from '../types'
16+
import { type EditorMode, injectKeyStore } from '../types'
1817
1918
const props = withDefaults(
2019
defineProps<{
@@ -37,7 +36,7 @@ const emit = defineEmits<{
3736
const containerRef = ref<HTMLDivElement>()
3837
const ready = ref(false)
3938
const editor = shallowRef<monaco.editor.IStandaloneCodeEditor>()
40-
const store = inject<Store>('store')!
39+
const store = inject(injectKeyStore)!
4140
4241
initMonaco(store)
4342
@@ -113,15 +112,15 @@ onMounted(async () => {
113112
() => props.filename,
114113
(_, oldFilename) => {
115114
if (!editorInstance) return
116-
const file = store.state.files[props.filename]
115+
const file = store.files[props.filename]
117116
if (!file) return null
118117
const model = getOrCreateModel(
119118
monaco.Uri.parse(`file:///${props.filename}`),
120119
file.language,
121120
file.code,
122121
)
123122
124-
const oldFile = oldFilename ? store.state.files[oldFilename] : null
123+
const oldFile = oldFilename ? store.files[oldFilename] : null
125124
if (oldFile) {
126125
oldFile.editorViewState = editorInstance.saveViewState()
127126
}

0 commit comments

Comments
 (0)