]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: repl (#94)
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sun, 21 Jan 2024 08:51:28 +0000 (16:51 +0800)
committerGitHub <noreply@github.com>
Sun, 21 Jan 2024 08:51:28 +0000 (16:51 +0800)
package.json
packages/compiler-sfc/package.json
packages/compiler-sfc/src/compileTemplate.ts
packages/sfc-playground/package.json
packages/sfc-playground/src/App.vue
packages/sfc-playground/src/Header.vue
packages/sfc-playground/src/vue-vapor-dev-proxy.ts [new file with mode: 0644]
packages/sfc-playground/vite.config.ts
pnpm-lock.yaml

index 1944a196264f9105d1befd91a25bfa9112285f6a..c461813d0b555f7dfbea093a6e9f765c1518272e 100644 (file)
@@ -38,7 +38,7 @@
     "build-all-cjs": "node scripts/build.js vue runtime compiler reactivity shared -af cjs",
     "build-runtime-esm": "node scripts/build.js runtime reactivity shared -af esm-bundler && node scripts/build.js vue -f esm-bundler-runtime && node scripts/build.js vue -f esm-browser-runtime",
     "build-browser-esm": "node scripts/build.js runtime reactivity shared -af esm-bundler && node scripts/build.js vue -f esm-bundler && node scripts/build.js vue -f esm-browser",
-    "build-ssr-esm": "node scripts/build.js compiler-sfc server-renderer -f esm-browser",
+    "build-ssr-esm": "node scripts/build.js compiler-sfc server-renderer vue-vapor -f esm-browser",
     "build-sfc-playground-self": "cd packages/sfc-playground && npm run build",
     "preinstall": "npx only-allow pnpm",
     "postinstall": "simple-git-hooks"
index e8d4096fe3e92a5772bfc62a369f22af5d779790..98f3205995db663fe37596ba737b988d528e4bde 100644 (file)
@@ -46,6 +46,7 @@
     "@vue/compiler-core": "workspace:*",
     "@vue/compiler-dom": "workspace:*",
     "@vue/compiler-ssr": "workspace:*",
+    "@vue/compiler-vapor": "workspace:*",
     "@vue/shared": "workspace:*",
     "estree-walker": "^2.0.2",
     "magic-string": "^0.30.5",
index 2d5ffdad7d8d4d78ce19fc446e49c172244afeae..65e9bd501189a7182987c4029779c3f2b50ff915 100644 (file)
@@ -27,6 +27,7 @@ import {
 } from './template/transformSrcset'
 import { generateCodeFrame, isObject } from '@vue/shared'
 import * as CompilerDOM from '@vue/compiler-dom'
+import * as CompilerVapor from '@vue/compiler-vapor'
 import * as CompilerSSR from '@vue/compiler-ssr'
 import consolidate from '@vue/consolidate'
 import { warnOnce } from './warn'
@@ -55,6 +56,7 @@ export interface SFCTemplateCompileOptions {
   scoped?: boolean
   slotted?: boolean
   isProd?: boolean
+  vapor?: boolean
   ssr?: boolean
   ssrCssVars?: string[]
   inMap?: RawSourceMap
@@ -171,6 +173,7 @@ function doCompileTemplate({
   source,
   ast: inAST,
   ssr = false,
+  vapor = false,
   ssrCssVars,
   isProd = false,
   compiler,
@@ -205,7 +208,12 @@ function doCompileTemplate({
   const shortId = id.replace(/^data-v-/, '')
   const longId = `data-v-${shortId}`
 
-  const defaultCompiler = ssr ? (CompilerSSR as TemplateCompiler) : CompilerDOM
+  const defaultCompiler = vapor
+    ? // TODO ssr
+      (CompilerVapor as TemplateCompiler)
+    : ssr
+      ? (CompilerSSR as TemplateCompiler)
+      : CompilerDOM
   compiler = compiler || defaultCompiler
 
   if (compiler !== defaultCompiler) {
index 1e6bd1a3733c07214c94e08dc4869946c434a8e2..c0cd7e10c42963b4db50cca88ff0786c19cc4a12 100644 (file)
@@ -13,7 +13,7 @@
     "vite": "^5.0.5"
   },
   "dependencies": {
-    "@vue/repl": "^3.1.1",
+    "@vue/repl": "4.0.0-alpha.0",
     "file-saver": "^2.0.5",
     "jszip": "^3.10.1",
     "vue": "workspace:*"
index cadd39f4baa6a322bfe96c46ed31c1bc3dcf96ac..c01f05680ac2be0728c4497fcf545efc358b940f 100644 (file)
@@ -1,10 +1,17 @@
 <script setup lang="ts">
 import Header from './Header.vue'
-import { Repl, ReplStore, SFCOptions } from '@vue/repl'
+import {
+  Repl,
+  type SFCOptions,
+  useStore,
+  useVueImportMap,
+  mergeImportMap,
+  File,
+  StoreState,
+} from '@vue/repl'
 import type Monaco from '@vue/repl/monaco-editor'
 import type CodeMirror from '@vue/repl/codemirror-editor'
-import { ref, watchEffect, onMounted } from 'vue'
-import { shallowRef } from 'vue'
+import { ref, watchEffect, onMounted, computed, shallowRef, watch } from 'vue'
 
 const EditorComponent = shallowRef<typeof Monaco | typeof CodeMirror>()
 
@@ -26,78 +33,135 @@ const setVH = () => {
 window.addEventListener('resize', setVH)
 setVH()
 
-const useProdMode = ref(false)
 const useSSRMode = ref(false)
+const useVaporMode = ref(false)
+
+const {
+  vueVersion,
+  productionMode,
+  importMap: vueImportMap,
+} = useVueImportMap({
+  runtimeDev: import.meta.env.PROD
+    ? `${location.origin}/vue.runtime.esm-browser.js`
+    : `${location.origin}/src/vue-dev-proxy`,
+  runtimeProd: import.meta.env.PROD
+    ? `${location.origin}/vue.runtime.esm-browser.prod.js`
+    : `${location.origin}/src/vue-dev-proxy-prod`,
+  serverRenderer: import.meta.env.PROD
+    ? `${location.origin}/server-renderer.esm-browser.js`
+    : `${location.origin}/src/vue-server-renderer-dev-proxy`,
+})
+
+const importMap = computed(() =>
+  mergeImportMap(vueImportMap.value, {
+    imports: {
+      'vue/vapor': import.meta.env.PROD
+        ? `${location.origin}/vue-vapor.esm-browser.js`
+        : `${location.origin}/src/vue-vapor-dev-proxy`,
+    },
+  }),
+)
 
 let hash = location.hash.slice(1)
 if (hash.startsWith('__DEV__')) {
   hash = hash.slice(7)
-  useProdMode.value = false
+  productionMode.value = false
 }
 if (hash.startsWith('__PROD__')) {
   hash = hash.slice(8)
-  useProdMode.value = true
+  productionMode.value = true
 }
 if (hash.startsWith('__SSR__')) {
   hash = hash.slice(7)
   useSSRMode.value = true
 }
+if (hash.startsWith('__VAPOR__')) {
+  hash = hash.slice(9)
+  useVaporMode.value = true
+}
 
-const store = new ReplStore({
-  serializedState: hash,
-  productionMode: useProdMode.value,
-  defaultVueRuntimeURL: import.meta.env.PROD
-    ? `${location.origin}/vue.runtime.esm-browser.js`
-    : `${location.origin}/src/vue-dev-proxy`,
-  defaultVueRuntimeProdURL: import.meta.env.PROD
-    ? `${location.origin}/vue.runtime.esm-browser.prod.js`
-    : `${location.origin}/src/vue-dev-proxy-prod`,
-  defaultVueServerRendererURL: import.meta.env.PROD
-    ? `${location.origin}/server-renderer.esm-browser.js`
-    : `${location.origin}/src/vue-server-renderer-dev-proxy`,
-})
+const files: StoreState['files'] = ref(Object.create(null))
+const mainFile = ref('src/App.vue')
 
 // enable experimental features
-const sfcOptions: SFCOptions = {
-  script: {
-    inlineTemplate: useProdMode.value,
-    isProd: useProdMode.value,
-    propsDestructure: true,
-  },
-  style: {
-    isProd: useProdMode.value,
-  },
-  template: {
-    isProd: useProdMode.value,
-    compilerOptions: {
-      isCustomElement: (tag: string) => tag === 'mjx-container',
+const sfcOptions = computed(
+  (): SFCOptions => ({
+    script: {
+      inlineTemplate: productionMode.value,
+      isProd: productionMode.value,
+      propsDestructure: true,
+    },
+    style: {
+      isProd: productionMode.value,
     },
+    template: {
+      vapor: useVaporMode.value,
+      isProd: productionMode.value,
+      compilerOptions: {
+        isCustomElement: (tag: string) => tag === 'mjx-container',
+      },
+    },
+  }),
+)
+
+const store = useStore(
+  {
+    files,
+    vueVersion,
+    builtinImportMap: importMap,
+    sfcOptions,
+    mainFile,
   },
-}
+  hash,
+)
+// @ts-expect-error
+globalThis.store = store
+
+watch(
+  useVaporMode,
+  () => {
+    if (useVaporMode.value) {
+      files.value['src/index.html'] = new File(
+        'src/index.html',
+        `<script type="module">
+        import { render } from 'vue/vapor'
+        import App from './App.vue'
+        render(App, {}, '#app')` +
+          '<' +
+          '/script>' +
+          `<div id="app"></div>`,
+        true,
+      )
+      mainFile.value = 'src/index.html'
+      store.activeFile = files.value['src/App.vue']
+    } else if (files.value['src/index.html']?.hidden) {
+      delete files.value['src/index.html']
+      mainFile.value = 'src/App.vue'
+    }
+  },
+  { immediate: true },
+)
 
 // persist state
 watchEffect(() => {
   const newHash = store
     .serialize()
+    .replace(/^#/, useVaporMode.value ? `#__VAPOR__` : `#`)
     .replace(/^#/, useSSRMode.value ? `#__SSR__` : `#`)
-    .replace(/^#/, useProdMode.value ? `#__PROD__` : `#`)
+    .replace(/^#/, productionMode.value ? `#__PROD__` : `#`)
   history.replaceState({}, '', newHash)
 })
 
 function toggleProdMode() {
-  const isProd = (useProdMode.value = !useProdMode.value)
-  sfcOptions.script!.inlineTemplate =
-    sfcOptions.script!.isProd =
-    sfcOptions.template!.isProd =
-    sfcOptions.style!.isProd =
-      isProd
-  store.toggleProduction()
-  store.setFiles(store.getFiles())
+  productionMode.value = !productionMode.value
 }
 
 function toggleSSR() {
   useSSRMode.value = !useSSRMode.value
-  store.setFiles(store.getFiles())
+}
+
+function toggleVapor() {
+  useVaporMode.value = !useVaporMode.value
 }
 
 function reloadPage() {
@@ -117,11 +181,13 @@ onMounted(() => {
 <template>
   <Header
     :store="store"
-    :prod="useProdMode"
+    :prod="productionMode"
     :ssr="useSSRMode"
+    :vapor="useVaporMode"
     @toggle-theme="toggleTheme"
     @toggle-prod="toggleProdMode"
     @toggle-ssr="toggleSSR"
+    @toggle-vapor="toggleVapor"
     @reload-page="reloadPage"
   />
   <Repl
@@ -135,7 +201,6 @@ onMounted(() => {
     :store="store"
     :showCompileOutput="true"
     :autoResize="true"
-    :sfcOptions="sfcOptions"
     :clearConsole="false"
     :preview-options="{
       customCode: {
index 48585a8e646a0a7437c8df1af450f26a8b51dcf7..000b2137c407b30e220227f7236d3566e146fad6 100644 (file)
@@ -14,11 +14,13 @@ const props = defineProps<{
   store: ReplStore
   prod: boolean
   ssr: boolean
+  vapor: boolean
 }>()
 const emit = defineEmits([
   'toggle-theme',
   'toggle-ssr',
   'toggle-prod',
+  'toggle-vapor',
   'reload-page',
 ])
 
@@ -27,7 +29,7 @@ const { store } = props
 const currentCommit = __COMMIT__
 const vueVersion = ref(`@${currentCommit}`)
 
-const vueURL = store.getImportMap().imports.vue
+const vueURL = store.getImportMap().imports?.vue
 if (vueURL && !vueURL.startsWith(location.origin)) {
   const versionMatch = vueURL.match(/runtime-dom@([^/]+)/)
   if (versionMatch) vueVersion.value = versionMatch[1]
@@ -35,12 +37,12 @@ if (vueURL && !vueURL.startsWith(location.origin)) {
 
 async function setVueVersion(v: string) {
   vueVersion.value = `loading...`
-  await store.setVueVersion(v)
+  store.vueVersion = v
   vueVersion.value = v
 }
 
 function resetVueVersion() {
-  store.resetVueVersion()
+  store.vueVersion = undefined
   vueVersion.value = `@${currentCommit}`
 }
 
@@ -73,7 +75,7 @@ function toggleDark() {
     </h1>
     <div class="links">
       <VersionSelect
-        v-model="store.state.typescriptVersion"
+        v-model="store.typescriptVersion"
         pkg="typescript"
         label="TypeScript Version"
       />
@@ -102,6 +104,14 @@ function toggleDark() {
       >
         <span>{{ prod ? 'PROD' : 'DEV' }}</span>
       </button>
+      <button
+        title="Toggle vapor mode"
+        class="toggle-vapor"
+        :class="{ enabled: vapor }"
+        @click="$emit('toggle-vapor')"
+      >
+        <span>{{ vapor ? 'VAPOR ON' : 'VAPOR OFF' }}</span>
+      </button>
       <button
         title="Toggle server rendering mode"
         class="toggle-ssr"
@@ -202,6 +212,7 @@ h1 img {
 }
 
 .toggle-prod span,
+.toggle-vapor span,
 .toggle-ssr span {
   font-size: 12px;
   border-radius: 4px;
@@ -226,6 +237,15 @@ h1 img {
   background-color: var(--green);
 }
 
+.toggle-vapor span {
+  background-color: var(--btn-bg);
+}
+
+.toggle-vapor.enabled span {
+  color: #fff;
+  background-color: var(--green);
+}
+
 .toggle-dark svg {
   width: 18px;
   height: 18px;
diff --git a/packages/sfc-playground/src/vue-vapor-dev-proxy.ts b/packages/sfc-playground/src/vue-vapor-dev-proxy.ts
new file mode 100644 (file)
index 0000000..cafc65f
--- /dev/null
@@ -0,0 +1,2 @@
+// serve vue/vapor to the iframe sandbox during dev.
+export * from 'vue/vapor'
index e30078f4b790787dbdf102a72b39b206caa8b5d0..5768c85c9f9daf284e041d48e7b9b7c65f4f0fbd 100644 (file)
@@ -52,6 +52,7 @@ function copyVuePlugin(): Plugin {
       copyFile(`../vue/dist/vue.runtime.esm-browser.js`)
       copyFile(`../vue/dist/vue.runtime.esm-browser.prod.js`)
       copyFile(`../server-renderer/dist/server-renderer.esm-browser.js`)
+      copyFile(`../vue-vapor/dist/vue-vapor.esm-browser.js`)
     },
   }
 }
index b087008046e02213709ba68cb34bbb58c2fc0e81..75dd6ed38f1ca18eecd4e686d35fc641c2f8c643 100644 (file)
@@ -219,6 +219,9 @@ importers:
       '@vue/compiler-ssr':
         specifier: workspace:*
         version: link:../compiler-ssr
+      '@vue/compiler-vapor':
+        specifier: workspace:*
+        version: link:../compiler-vapor
       '@vue/shared':
         specifier: workspace:*
         version: link:../shared
@@ -368,8 +371,8 @@ importers:
   packages/sfc-playground:
     dependencies:
       '@vue/repl':
-        specifier: ^3.1.1
-        version: 3.3.0
+        specifier: 4.0.0-alpha.0
+        version: 4.0.0-alpha.0
       file-saver:
         specifier: ^2.0.5
         version: 2.0.5
@@ -1634,8 +1637,8 @@ packages:
     engines: {node: '>= 0.12.0'}
     dev: true
 
-  /@vue/repl@3.3.0:
-    resolution: {integrity: sha512-A9tdO7obt/kpFUHdgGoRnan6bZjfz/WAJ5+DpPkvgNEc960W+bJraURv8MUVtH2Id/byWotKbUve2jTakiccSw==}
+  /@vue/repl@4.0.0-alpha.0:
+    resolution: {integrity: sha512-kGgnon2yV1y0eKeWatys4by32XXCDSdq31Rwx0cd8xXAIK0GIL0AeSMCvVUrNE2ke8rFVYe6xMmpOd1iCcM0Zg==}
     dev: false
 
   /@zeit/schemas@2.29.0: