From f76ddc5ac32ff2af9364ba12cdc2240e2f13b88e Mon Sep 17 00:00:00 2001 From: Evan You Date: Sun, 28 Mar 2021 01:35:45 -0400 Subject: [PATCH] workflow: sfc playground --- .eslintrc.js | 2 +- package.json | 2 +- packages/compiler-sfc/package.json | 3 +- packages/compiler-sfc/src/compileScript.ts | 2 +- packages/compiler-sfc/src/index.ts | 6 + packages/compiler-sfc/src/warn.ts | 4 + packages/global.d.ts | 7 + packages/sfc-playground/index.html | 19 + packages/sfc-playground/package.json | 23 + packages/sfc-playground/src/App.vue | 37 ++ packages/sfc-playground/src/Header.vue | 23 + packages/sfc-playground/src/Message.vue | 66 +++ packages/sfc-playground/src/SplitPane.vue | 91 ++++ .../src/codemirror/CodeMirror.vue | 76 +++ .../src/codemirror/codemirror.css | 506 ++++++++++++++++++ .../src/codemirror/codemirror.ts | 19 + packages/sfc-playground/src/editor/Editor.vue | 17 + packages/sfc-playground/src/main.ts | 4 + packages/sfc-playground/src/output/Output.vue | 57 ++ .../sfc-playground/src/output/Preview.vue | 108 ++++ .../sfc-playground/src/output/PreviewProxy.ts | 96 ++++ .../sfc-playground/src/output/srcdoc.html | 201 +++++++ packages/sfc-playground/src/store.ts | 168 ++++++ packages/sfc-playground/src/utils.ts | 9 + packages/sfc-playground/src/vue-dev-proxy.ts | 2 + packages/sfc-playground/vite.config.ts | 34 ++ rollup.config.js | 2 +- yarn.lock | 93 +++- 28 files changed, 1654 insertions(+), 23 deletions(-) create mode 100644 packages/sfc-playground/index.html create mode 100644 packages/sfc-playground/package.json create mode 100644 packages/sfc-playground/src/App.vue create mode 100644 packages/sfc-playground/src/Header.vue create mode 100644 packages/sfc-playground/src/Message.vue create mode 100644 packages/sfc-playground/src/SplitPane.vue create mode 100644 packages/sfc-playground/src/codemirror/CodeMirror.vue create mode 100644 packages/sfc-playground/src/codemirror/codemirror.css create mode 100644 packages/sfc-playground/src/codemirror/codemirror.ts create mode 100644 packages/sfc-playground/src/editor/Editor.vue create mode 100644 packages/sfc-playground/src/main.ts create mode 100644 packages/sfc-playground/src/output/Output.vue create mode 100644 packages/sfc-playground/src/output/Preview.vue create mode 100644 packages/sfc-playground/src/output/PreviewProxy.ts create mode 100644 packages/sfc-playground/src/output/srcdoc.html create mode 100644 packages/sfc-playground/src/store.ts create mode 100644 packages/sfc-playground/src/utils.ts create mode 100644 packages/sfc-playground/src/vue-dev-proxy.ts create mode 100644 packages/sfc-playground/vite.config.ts diff --git a/.eslintrc.js b/.eslintrc.js index 98f42a74..0732923e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -56,7 +56,7 @@ module.exports = { }, // Private package, browser only + no syntax restrictions { - files: ['packages/template-explorer/**'], + files: ['packages/template-explorer/**', 'packages/sfc-playground/**'], rules: { 'no-restricted-globals': ['error', ...NodeGlobals], 'no-restricted-syntax': 'off' diff --git a/package.json b/package.json index 1e94678b..64833210 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "rollup": "~2.38.5", "rollup-plugin-node-builtins": "^2.1.2", "rollup-plugin-node-globals": "^1.4.0", - "rollup-plugin-node-polyfills": "^0.2.1", + "rollup-plugin-polyfill-node": "^0.6.2", "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.27.2", "semver": "^7.3.2", diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index ad30f374..229c2bea 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -3,6 +3,7 @@ "version": "3.0.9", "description": "@vue/compiler-sfc", "main": "dist/compiler-sfc.cjs.js", + "module": "dist/compiler-sfc.esm-browser.js", "types": "dist/compiler-sfc.d.ts", "files": [ "dist" @@ -11,7 +12,7 @@ "name": "VueCompilerSFC", "formats": [ "cjs", - "global" + "esm-browser" ], "prod": false, "enableNonBrowserBranches": true diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 864fcbbb..0cfe2489 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1367,7 +1367,7 @@ function markScopeIdentifier( * but with some subtle differences as this needs to handle a wider range of * possible syntax. */ -function walkIdentifiers( +export function walkIdentifiers( root: Node, onIdentifier: (node: Identifier, parent: Node, parentStack: Node[]) => void ) { diff --git a/packages/compiler-sfc/src/index.ts b/packages/compiler-sfc/src/index.ts index 2015dcb6..a38d968a 100644 --- a/packages/compiler-sfc/src/index.ts +++ b/packages/compiler-sfc/src/index.ts @@ -6,6 +6,12 @@ export { compileScript } from './compileScript' export { rewriteDefault } from './rewriteDefault' export { generateCodeFrame } from '@vue/compiler-core' +// Utilities +export { parse as babelParse } from '@babel/parser' +export { walkIdentifiers } from './compileScript' +import MagicString from 'magic-string' +export { MagicString } + // Types export { SFCParseOptions, diff --git a/packages/compiler-sfc/src/warn.ts b/packages/compiler-sfc/src/warn.ts index d53f376c..dd9d3b5a 100644 --- a/packages/compiler-sfc/src/warn.ts +++ b/packages/compiler-sfc/src/warn.ts @@ -16,6 +16,10 @@ export function warn(msg: string) { } export function warnExperimental(feature: string, rfcId: number) { + // eslint-disable-next-line + if (typeof window !== 'undefined') { + return + } warnOnce( `${feature} is still an experimental proposal.\n` + `Follow its status at https://github.com/vuejs/rfcs/pull/${rfcId}.` diff --git a/packages/global.d.ts b/packages/global.d.ts index 2957d35a..e6239ffd 100644 --- a/packages/global.d.ts +++ b/packages/global.d.ts @@ -22,3 +22,10 @@ declare namespace jest { toHaveBeenWarnedTimes(n: number): R } } + +declare module '*.vue' { + +} +declare module '*?raw' { + +} diff --git a/packages/sfc-playground/index.html b/packages/sfc-playground/index.html new file mode 100644 index 00000000..15915f30 --- /dev/null +++ b/packages/sfc-playground/index.html @@ -0,0 +1,19 @@ + + + + + + + Vue SFC Playground + + + + + + + + + +
+ + \ No newline at end of file diff --git a/packages/sfc-playground/package.json b/packages/sfc-playground/package.json new file mode 100644 index 00000000..5d3fed8f --- /dev/null +++ b/packages/sfc-playground/package.json @@ -0,0 +1,23 @@ +{ + "name": "@vue/sfc-playground", + "version": "3.0.9", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview" + }, + "buildOptions": { + "formats": [ + "global" + ], + "env": "development", + "enableNonBrowserBranches": true + }, + "devDependencies": { + "@types/codemirror": "^0.0.108", + "@vitejs/plugin-vue": "^1.2.0", + "codemirror": "^5.60.0", + "vite": "^2.1.3" + } +} diff --git a/packages/sfc-playground/src/App.vue b/packages/sfc-playground/src/App.vue new file mode 100644 index 00000000..e032f703 --- /dev/null +++ b/packages/sfc-playground/src/App.vue @@ -0,0 +1,37 @@ + + + + + \ No newline at end of file diff --git a/packages/sfc-playground/src/Header.vue b/packages/sfc-playground/src/Header.vue new file mode 100644 index 00000000..1a1ecc8f --- /dev/null +++ b/packages/sfc-playground/src/Header.vue @@ -0,0 +1,23 @@ + + + diff --git a/packages/sfc-playground/src/Message.vue b/packages/sfc-playground/src/Message.vue new file mode 100644 index 00000000..e2512b18 --- /dev/null +++ b/packages/sfc-playground/src/Message.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/packages/sfc-playground/src/SplitPane.vue b/packages/sfc-playground/src/SplitPane.vue new file mode 100644 index 00000000..78a98dbf --- /dev/null +++ b/packages/sfc-playground/src/SplitPane.vue @@ -0,0 +1,91 @@ + + + + + \ No newline at end of file diff --git a/packages/sfc-playground/src/codemirror/CodeMirror.vue b/packages/sfc-playground/src/codemirror/CodeMirror.vue new file mode 100644 index 00000000..18e3b3db --- /dev/null +++ b/packages/sfc-playground/src/codemirror/CodeMirror.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/packages/sfc-playground/src/codemirror/codemirror.css b/packages/sfc-playground/src/codemirror/codemirror.css new file mode 100644 index 00000000..01fa7624 --- /dev/null +++ b/packages/sfc-playground/src/codemirror/codemirror.css @@ -0,0 +1,506 @@ +/* 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; +} diff --git a/packages/sfc-playground/src/codemirror/codemirror.ts b/packages/sfc-playground/src/codemirror/codemirror.ts new file mode 100644 index 00000000..0ae44dd8 --- /dev/null +++ b/packages/sfc-playground/src/codemirror/codemirror.ts @@ -0,0 +1,19 @@ +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 diff --git a/packages/sfc-playground/src/editor/Editor.vue b/packages/sfc-playground/src/editor/Editor.vue new file mode 100644 index 00000000..8be5d0fc --- /dev/null +++ b/packages/sfc-playground/src/editor/Editor.vue @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/packages/sfc-playground/src/main.ts b/packages/sfc-playground/src/main.ts new file mode 100644 index 00000000..01433bca --- /dev/null +++ b/packages/sfc-playground/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/packages/sfc-playground/src/output/Output.vue b/packages/sfc-playground/src/output/Output.vue new file mode 100644 index 00000000..5116ea2f --- /dev/null +++ b/packages/sfc-playground/src/output/Output.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/packages/sfc-playground/src/output/Preview.vue b/packages/sfc-playground/src/output/Preview.vue new file mode 100644 index 00000000..8ef3b101 --- /dev/null +++ b/packages/sfc-playground/src/output/Preview.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/packages/sfc-playground/src/output/PreviewProxy.ts b/packages/sfc-playground/src/output/PreviewProxy.ts new file mode 100644 index 00000000..338da5ab --- /dev/null +++ b/packages/sfc-playground/src/output/PreviewProxy.ts @@ -0,0 +1,96 @@ +// 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 + pending_cmds: Map< + number, + { resolve: (value: unknown) => void; reject: (reason?: any) => void } + > + handle_event: (e: any) => void + + constructor(iframe: HTMLIFrameElement, handlers: Record) { + 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) { + return this.iframe_command('eval', { script }) + } + + handle_links() { + return this.iframe_command('catch_clicks', {}) + } +} diff --git a/packages/sfc-playground/src/output/srcdoc.html b/packages/sfc-playground/src/output/srcdoc.html new file mode 100644 index 00000000..7ef13dce --- /dev/null +++ b/packages/sfc-playground/src/output/srcdoc.html @@ -0,0 +1,201 @@ + + + + + + + + +
+ + \ No newline at end of file diff --git a/packages/sfc-playground/src/store.ts b/packages/sfc-playground/src/store.ts new file mode 100644 index 00000000..4774149c --- /dev/null +++ b/packages/sfc-playground/src/store.ts @@ -0,0 +1,168 @@ +import { reactive, watchEffect } from 'vue' +import { + parse, + compileTemplate, + compileStyleAsync, + compileScript, + rewriteDefault, + CompilerError +} from '@vue/compiler-sfc' + +const storeKey = 'sfc-code' +const saved = localStorage.getItem(storeKey) || '' + +// @ts-ignore +export const sandboxVueURL = import.meta.env.PROD + ? '/vue.runtime.esm-browser.js' // to be copied on build + : '/src/vue-dev-proxy' + +export const store = reactive({ + code: saved, + compiled: { + executed: '', + js: '', + css: '', + template: '' + }, + errors: [] as (string | CompilerError | SyntaxError)[] +}) + +const filename = 'Playground.vue' +const id = 'scope-id' +const compIdentifier = `__comp` + +watchEffect(async () => { + const { code, compiled } = store + if (!code.trim()) { + return + } + + localStorage.setItem(storeKey, code) + + const { errors, descriptor } = parse(code, { filename, sourceMap: true }) + if (errors.length) { + store.errors = errors + return + } + + const hasScoped = descriptor.styles.some(s => s.scoped) + let finalCode = '' + + if ( + (descriptor.script && descriptor.script.lang) || + (descriptor.scriptSetup && descriptor.scriptSetup.lang) || + descriptor.styles.some(s => s.lang) || + (descriptor.template && descriptor.template.lang) + ) { + store.errors = [ + 'lang="x" pre-processors are not supported in the in-browser playground.' + ] + return + } + + // script + if (descriptor.script || descriptor.scriptSetup) { + try { + const compiledScript = compileScript(descriptor, { + id, + refSugar: true, + inlineTemplate: true + }) + compiled.js = compiledScript.content.trim() + finalCode += + `\n` + + rewriteDefault( + rewriteVueImports(compiledScript.content), + compIdentifier + ) + } catch (e) { + store.errors = [e] + return + } + } else { + compiled.js = '' + finalCode += `\nconst ${compIdentifier} = {}` + } + + // template + if (descriptor.template && !descriptor.scriptSetup) { + const templateResult = compileTemplate({ + source: descriptor.template.content, + filename, + id, + scoped: hasScoped, + slotted: descriptor.slotted, + isProd: false + }) + if (templateResult.errors.length) { + store.errors = templateResult.errors + return + } + + compiled.template = templateResult.code.trim() + finalCode += rewriteVueImports(templateResult.code).replace( + /\nexport (function|const) render/, + '$1 render' + ) + finalCode += `\n${compIdentifier}.render = render` + } else { + compiled.template = descriptor.scriptSetup + ? '/* inlined in JS (script setup) */' + : '/* no template present */' + } + if (hasScoped) { + finalCode += `\n${compIdentifier}.__scopeId = ${JSON.stringify( + `data-v-${id}` + )}` + } + + // styles + let css = '' + for (const style of descriptor.styles) { + if (style.module) { + // TODO error + continue + } + + const styleResult = await 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() + finalCode += `\ndocument.getElementById('__sfc-styles').innerHTML = ${JSON.stringify( + css + )}` + } else { + compiled.css = '' + } + + store.errors = [] + if (finalCode) { + compiled.executed = + `/* Exact code being executed in the preview iframe (different from production bundler output) */\n` + + finalCode + } +}) + +// TODO use proper parser +function rewriteVueImports(code: string): string { + return code.replace( + /\b(import \{.*?\}\s+from\s+)(?:"vue"|'vue')/g, + `$1"${sandboxVueURL}"` + ) +} diff --git a/packages/sfc-playground/src/utils.ts b/packages/sfc-playground/src/utils.ts new file mode 100644 index 00000000..c5f1de10 --- /dev/null +++ b/packages/sfc-playground/src/utils.ts @@ -0,0 +1,9 @@ +export function debounce(fn: Function, n = 100) { + let handle: any + return (...args: any[]) => { + if (handle) clearTimeout(handle) + handle = setTimeout(() => { + fn(...args) + }, n) + } +} diff --git a/packages/sfc-playground/src/vue-dev-proxy.ts b/packages/sfc-playground/src/vue-dev-proxy.ts new file mode 100644 index 00000000..f254416d --- /dev/null +++ b/packages/sfc-playground/src/vue-dev-proxy.ts @@ -0,0 +1,2 @@ +// serve vue to the iframe sandbox during dev. +export * from 'vue' diff --git a/packages/sfc-playground/vite.config.ts b/packages/sfc-playground/vite.config.ts new file mode 100644 index 00000000..1e4c0472 --- /dev/null +++ b/packages/sfc-playground/vite.config.ts @@ -0,0 +1,34 @@ +import fs from 'fs' +import path from 'path' +import { defineConfig, Plugin } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue(), copyVuePlugin()], + optimizeDeps: { + exclude: ['consolidate'] + } +}) + +function copyVuePlugin(): Plugin { + return { + name: 'copy-vue', + generateBundle(_opts, bundle) { + const filePath = path.resolve( + __dirname, + '../vue/dist/vue.runtime.esm-browser.js' + ) + if (!fs.existsSync(filePath)) { + throw new Error( + `vue.runtime.esm-browser.js not built. ` + + `Run "yarn build vue -f esm-browser" first.` + ) + } + this.emitFile({ + type: 'asset', + fileName: 'vue.runtime.esm-browser.js', + source: fs.readFileSync(filePath, 'utf-8') + }) + } + } +} diff --git a/rollup.config.js b/rollup.config.js index dead7117..e8fe8d88 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -142,7 +142,7 @@ function createConfig(format, output, plugins = []) { require('@rollup/plugin-commonjs')({ sourceMap: false }), - require('rollup-plugin-node-polyfills')(), + require('rollup-plugin-polyfill-node')(), require('@rollup/plugin-node-resolve').nodeResolve() ] : [] diff --git a/yarn.lock b/yarn.lock index 202d0124..98966e2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -572,6 +572,15 @@ magic-string "^0.25.7" resolve "^1.17.0" +"@rollup/plugin-inject@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-inject/-/plugin-inject-4.0.2.tgz#55b21bb244a07675f7fdde577db929c82fc17395" + integrity sha512-TSLMA8waJ7Dmgmoc8JfPnwUwVZgLjjIAM6MqeIFqPO2ODK36JqE0Cf2F54UTgCUuW8da93Mvoj75a6KAVWgylw== + dependencies: + "@rollup/pluginutils" "^3.0.4" + estree-walker "^1.0.1" + magic-string "^0.25.5" + "@rollup/plugin-json@^4.0.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3" @@ -599,7 +608,7 @@ "@rollup/pluginutils" "^3.1.0" magic-string "^0.25.7" -"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": +"@rollup/pluginutils@^3.0.4", "@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== @@ -698,6 +707,13 @@ resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.33.tgz#d79c020f283bd50bd76101d7d300313c107325fc" integrity sha512-ndEo1xvnYeHxm7I/5sF6tBvnsA4Tdi3zj1keRKRs12SP+2ye2A27NDJ1B6PqkfMbGAcT+mqQVqbZRIrhfOp5PQ== +"@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": version "0.14.0" resolved "https://registry.yarnpkg.com/@types/consolidate/-/consolidate-0.14.0.tgz#856735b3a1421513bd12b9bdd588923bec773bff" @@ -814,6 +830,13 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== +"@types/tern@*": + version "0.23.3" + resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.3.tgz#4b54538f04a88c9ff79de1f6f94f575a7f339460" + integrity sha512-imDtS4TAoTcXk0g7u4kkWqedB3E4qpjXzCpD2LU5M5NAXHzCDsypyvXSaG7mM8DKYkCRa7tFp4tS/lp/Wo7Q3w== + dependencies: + "@types/estree" "*" + "@types/yargs-parser@*": version "20.2.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9" @@ -870,6 +893,11 @@ "@typescript-eslint/types" "4.19.0" eslint-visitor-keys "^2.0.0" +"@vitejs/plugin-vue@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-1.2.0.tgz#f0a92470b74761f90afc8cda204fa3bec9df09f4" + integrity sha512-IhSJfJH6IDNEAnhr91+2vhLLe/1SqkA/2BP19jwtn54DGI+cNbZIxiPhHIdKUpdRo0QwErOh6Jy1Maxk2uVo7A== + "@zeit/schemas@2.6.0": version "2.6.0" resolved "https://registry.yarnpkg.com/@zeit/schemas/-/schemas-2.6.0.tgz#004e8e553b4cd53d538bd38eac7bcbf58a867fe3" @@ -1696,6 +1724,11 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= +codemirror@^5.60.0: + version "5.60.0" + resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.60.0.tgz#00a8cfd287d5d8737ceb73987f04aee2fe5860da" + integrity sha512-AEL7LhFOlxPlCL8IdTcJDblJm8yrAGib7I+DErJPdZd4l6imx8IMgKK3RblVgBQqz3TZJR4oknQ03bz+uNjBYA== + collect-v8-coverage@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" @@ -1733,7 +1766,7 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colorette@^1.2.1: +colorette@^1.2.1, colorette@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== @@ -2419,6 +2452,11 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +esbuild@^0.9.3: + version "0.9.7" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.9.7.tgz#ea0d639cbe4b88ec25fbed4d6ff00c8d788ef70b" + integrity sha512-VtUf6aQ89VTmMLKrWHYG50uByMF4JQlVysb8dmg6cOgW8JnFCipmz7p+HNBl+RR3LLCuBxFGVauAe2wfnF9bLg== + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -4617,7 +4655,7 @@ magic-string@^0.22.5: dependencies: vlq "^0.2.2" -magic-string@^0.25.3, magic-string@^0.25.7: +magic-string@^0.25.5, magic-string@^0.25.7: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== @@ -5460,6 +5498,15 @@ postcss@^8.1.10: nanoid "^3.1.20" source-map "^0.6.1" +postcss@^8.2.1: + version "8.2.8" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.8.tgz#0b90f9382efda424c4f0f69a2ead6f6830d08ece" + integrity sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw== + dependencies: + colorette "^1.2.2" + nanoid "^3.1.20" + source-map "^0.6.1" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -6053,15 +6100,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rollup-plugin-inject@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz#e4233855bfba6c0c12a312fd6649dff9a13ee9f4" - integrity sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w== - dependencies: - estree-walker "^0.6.1" - magic-string "^0.25.3" - rollup-pluginutils "^2.8.1" - rollup-plugin-node-builtins@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/rollup-plugin-node-builtins/-/rollup-plugin-node-builtins-2.1.2.tgz#24a1fed4a43257b6b64371d8abc6ce1ab14597e9" @@ -6084,12 +6122,12 @@ rollup-plugin-node-globals@^1.4.0: process-es6 "^0.11.6" rollup-pluginutils "^2.3.1" -rollup-plugin-node-polyfills@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz#53092a2744837164d5b8a28812ba5f3ff61109fd" - integrity sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA== +rollup-plugin-polyfill-node@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-polyfill-node/-/rollup-plugin-polyfill-node-0.6.2.tgz#dea62e00f5cc2c174e4b4654b5daab79b1a92fc3" + integrity sha512-gMCVuR0zsKq0jdBn8pSXN1Ejsc458k2QsFFvQdbHoM0Pot5hEnck+pBP/FDwFS6uAi77pD3rDTytsaUStsOMlA== dependencies: - rollup-plugin-inject "^3.0.0" + "@rollup/plugin-inject" "^4.0.0" rollup-plugin-terser@^7.0.2: version "7.0.2" @@ -6112,13 +6150,20 @@ rollup-plugin-typescript2@^0.27.2: resolve "1.17.0" tslib "2.0.1" -rollup-pluginutils@^2.3.1, rollup-pluginutils@^2.8.1: +rollup-pluginutils@^2.3.1: version "2.8.2" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== dependencies: estree-walker "^0.6.1" +rollup@^2.38.5: + version "2.43.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.43.0.tgz#05d1ed0bbb37080a63e68c530a84d34f61ceb56a" + integrity sha512-FRsYGqlo1iF/w3bv319iStAK0hyhhwon35Cbo7sGUoXaOpsZFy6Lel7UoGb5bNDE4OsoWjMH94WiVvpOM26l3g== + optionalDependencies: + fsevents "~2.3.1" + rollup@~2.38.5: version "2.38.5" resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.38.5.tgz#be41ad4fe0c103a8794377afceb5f22b8f603d6a" @@ -7141,6 +7186,18 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vite@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.1.3.tgz#a31a844d26d3846b5a78f06970d1ea1f8a442955" + integrity sha512-bUzArZIUwADVJS/3ywCr4KKFn3a7izs4M87ZDlAlY2V34E4g1kH6p3sVNAh8/IXCn/56fwgMh3rRavPUW7qEQQ== + dependencies: + esbuild "^0.9.3" + postcss "^8.2.1" + resolve "^1.19.0" + rollup "^2.38.5" + optionalDependencies: + fsevents "~2.3.1" + vlq@^0.2.2: version "0.2.3" resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"