Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: useStore & useVueImportMap composable #207

Merged
merged 7 commits into from Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking change: remove sfcOptions on component but moved to Store

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
},
}
})
Comment on lines -62 to -76
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to store


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