Skip to content

feat(docs): conditionally load babel-standalone only on browsers that need transpilation #2294

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

Merged
merged 6 commits into from
Dec 15, 2018
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 45 additions & 36 deletions docs/pages/play.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,25 @@
You can clone docs repo, to hack and develop components.
changes will be reflected and hot-reloaded instantly.
</p>
</div>
<div class="col-12">
<div v-if="loading" class="alert alert-info show text-center">
<strong>Loading JavaScript Compiler...</strong>
</div>
</div>
<div class="col-12">
<form
class="d-inline-block ml-2 mr-0 p-0 float-right"
method="post"
action="https://jsfiddle.net/api/post/library/pure/"
target="_blank">
<input type="hidden" name="html" :value="fiddle_html">
<input type="hidden" name="js" :value="fiddle_js">
<input type="hidden" name="resources" :value="fiddle_dependencies">
<input type="hidden" name="css" value="body { padding: 1rem; }">
<input type="hidden" name="js_wrap" value="l">
<b-btn size="sm" type="submit" :disabled="!isOk">Export to JSFiddle</b-btn>
</form>
<b-btn @click="reset" size="sm" variant="danger" :disabled="isDefault">Reset to default</b-btn>
<div v-else class="clearfix">
<form class="d-inline-block ml-2 mr-0 p-0 float-right"
method="post"
action="https://jsfiddle.net/api/post/library/pure/"
target="_blank">
<input type="hidden" name="html" :value="fiddle_html">
<input type="hidden" name="js" :value="fiddle_js">
<input type="hidden" name="resources" :value="fiddle_dependencies">
<input type="hidden" name="css" value="body { padding: 1rem; }">
<input type="hidden" name="js_wrap" value="l">
<b-btn size="sm" type="submit" :disabled="!isOk">Export to JSFiddle</b-btn>
</form>
<b-btn @click="reset" size="sm" variant="danger" :disabled="isDefault">Reset to default</b-btn>
</div>
</div>
</div>

Expand Down Expand Up @@ -163,6 +164,7 @@
<script>
import Vue from 'vue'
import debounce from 'lodash/debounce'
import needsTranspiler from '../utils/needs-transpiler'

const defaultJS = `{
data () {
Expand All @@ -188,7 +190,7 @@ const defaultJS = `{
}`

const defaultHTML = `<div>
<b-button @click="toggle">
<b-button @click="toggle" size="sm">
{{ show ? 'Hide' : 'Show' }} Alert
</b-button>
<b-alert v-model="show"
Expand Down Expand Up @@ -301,30 +303,26 @@ export default {
this.playVM = null
this.contentUnWatch = null
this.run = () => {}
// Default code "transpiler"
this.compiler = (code) => code
},
mounted () {
this.$nextTick(() => {
// Start the loading indicator
this.loading = true
this.$nuxt.$loading.start()
// Lazy load the babel transpiler
import('../utils/compile-js').then((module) => {
// Update compiler reference
this.compiler = module.default
// Create our debounced runner
this.run = debounce(this._run, 500)
// Set up our editor content watcher.
this.contentUnWatch = this.$watch(
() => this.js.trim() + '::' + this.html.trim(),
(newVal, oldVal) => { this.run() }
)
// Stop the loading indicator
this.$nuxt.$loading.finish()
this.loading = false
// load our content into the editors
this.$nextTick(this.load)
})
if (needsTranspiler) {
// Start the loading indicator
this.loading = true
// Lazy load the babel transpiler
import('../utils/compile-js').then((module) => {
// Update compiler reference
this.compiler = module.default
// Run the setup code
this.doSetup()
// Stop the loading indicator
this.loading = false
})
} else {
this.doSetup()
}
})
},
beforeDestroy () {
Expand All @@ -336,6 +334,17 @@ export default {
}
},
methods: {
doSetup () {
// Create our debounced runner
this.run = debounce(this._run, 500)
// Set up our editor content watcher.
this.contentUnWatch = this.$watch(
() => this.js.trim() + '::' + this.html.trim(),
(newVal, oldVal) => { this.run() }
)
// load our content into the editors
this.$nextTick(this.load)
},
destroyVM () {
let vm = this.playVM
if (vm) {
Expand Down
122 changes: 68 additions & 54 deletions docs/plugins/play.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Vue from 'vue'
import debounce from 'lodash/debounce'
import hljs from 'highlightjs'
import needsTranspiler from '../utils/needs-transpiler'

const NAME_REGEX = /<!-- (.*)\.vue -->/
const NAME_DEFINITION_REGEX = /<!-- .*\.vue -->/
Expand All @@ -13,7 +14,7 @@ const CLASS_NAMES = {
error: 'error',
}

// Temporary compiler code, until directive is loaded/applied
// Default "transpiler" function
let compiler = (code) => code

const match = (regex, text) => (regex.exec(text) || [])[1]
Expand Down Expand Up @@ -79,68 +80,81 @@ const destroyVM = (name, vm) => {
[...document.querySelectorAll(`.vue-example-${name}`)].forEach(removeNode)
}

Vue.directive('play', (el, binding, vnode, oldVnode) => {
import('../utils/compile-js').then((module) => {
// Save the compiler reference for template parser
compiler = module.default

// Get all code-snippets
const pres = [...el.querySelectorAll('pre.hljs')]

// Iterate over them and parse
pres.forEach(pre => {
// Store example name globally
const name = match(NAME_REGEX, pre.textContent)

// Exit early when no name is given
if (!name) {
return
}
const processExamples = (el, binding, vnode, oldVnode) => {
if (vnode.context.$options['beforeDestroy']) {
vnode.context.$options['beforeDestroy'] = [].concat(vnode.context.$options['beforeDestroy']).filter(h => h)
} else {
vnode.context.$options['beforeDestroy'] = []
}

// Remove name defintion
let text = pre.textContent.replace(NAME_DEFINITION_REGEX, '').trim()
pre.textContent = text
// Get all code-snippets
const pres = [...el.querySelectorAll('pre.hljs')]

// Highlight again
hljs.highlightBlock(pre)
// Iterate over them and parse
pres.forEach(pre => {
// Store example name globally
const name = match(NAME_REGEX, pre.textContent)

// Add editable class
pre.classList.add(CLASS_NAMES.editable)
// Exit early when no name is given
if (!name) {
return
}

// Initial load
let vm = createVM(name, pre, vnode)
// Remove name defintion
let text = pre.textContent.replace(NAME_DEFINITION_REGEX, '').trim()
pre.textContent = text

if (!Array.isArray(vnode.context.$options['beforeDestroy'])) {
vnode.context.$options['beforeDestroy'] = []
}
// Highlight again
hljs.highlightBlock(pre)

vnode.context.$options['beforeDestroy'].push(() => destroyVM(name, vm))
// Add editable class
pre.classList.add(CLASS_NAMES.editable)

// Enable live edit on double click
pre.ondblclick = async () => {
// Add live class
pre.classList.add(CLASS_NAMES.live)
// Make editable
pre.contentEditable = true
// Initial load
let vm = createVM(name, pre, vnode)

pre.onblur = () => {
// Rehighlight
hljs.highlightBlock(pre)
}
// Ensure we destroy the VM when parent is destroued
vnode.context.$options['beforeDestroy'].push(() => destroyVM(name, vm))

// Enable live edit on double click
pre.ondblclick = async () => {
// Add live class
pre.classList.add(CLASS_NAMES.live)
// Make editable
pre.contentEditable = true

pre.onkeyup = debounce(() => {
// Recreate VM
destroyVM(name, vm)
vm = createVM(name, pre, vnode)

// Toggle error class
if (vm === null) {
pre.classList.add(CLASS_NAMES.error)
} else {
pre.classList.remove(CLASS_NAMES.error)
}
}, 250)
pre.onblur = () => {
// Rehighlight
hljs.highlightBlock(pre)
}
})

pre.onkeyup = debounce(() => {
// Recreate VM
destroyVM(name, vm)
vm = createVM(name, pre, vnode)

// Toggle error class
if (vm === null) {
pre.classList.add(CLASS_NAMES.error)
} else {
pre.classList.remove(CLASS_NAMES.error)
}
}, 500)
}
})
}

// Register our v-play directive
Vue.directive('play', (el, binding, vnode, oldVnode) => {
if (needsTranspiler) {
import('../utils/compile-js').then((module) => {
// Save the compiler reference for template parser
compiler = module.default
// Convert examples to live/editable
processExamples(el, binding, vnode, oldVnode)
})
} else {
// Convert examples to live/editable
processExamples(el, binding, vnode, oldVnode)
}
})
35 changes: 35 additions & 0 deletions docs/utils/needs-transpiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Determine if the broser needs to use @babel/standalone compiler for v-play and playground

let needsTranspiler = false

// Tests to see if we need to compile ES6 to ES5. Tests for commonly used ES6 features.
// If any test fails, then we need to transpile code with @babel/standalone.
const tests = [
// Arrow functions
'const test1 = (a) => a',
// Object function shortcut
'const test2 = { a: 1, b () { return 0 } }',
// Object shortcut
'const test3a = { a: 1}; const test3b = { test3a, b: 2 }',
// Object rest spread
'const test4a = { a: 1, b: 2}; const test4b = { c: 3, ...test4a }',
// String interpolation
/* eslint-disable no-template-curly-in-string */
'const test5a = "bar"; const test5b = `foo${test5a}`'
/* eslint-enable no-template-curly-in-string */
]

// Run tests to see if transpilation is needed. Returns after first test that fails
if (typeof window !== 'undefined') {
/* eslint-disable no-eval */
for (let i = 0; i < tests.length && !needsTranspiler; i++) {
try {
eval(tests[i])
} catch (e) {
needsTranspiler = true
}
}
/* eslint-enable no-eval */
}

export default needsTranspiler