Skip to content

Commit

Permalink
Add safari extension (vuejs#352)
Browse files Browse the repository at this point in the history
* safari extension

* move src and webpack config outside of extension

* fix production build; fix eslint errors

* add comment

* Update package.json
  • Loading branch information
jaredhobbs authored and yyx990803 committed Jun 20, 2017
1 parent 6f29de5 commit 4246f81
Show file tree
Hide file tree
Showing 15 changed files with 318 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Currently only a Chrome devtools extension is available.

[Workaround for Firefox](https://github.com/vuejs/vue-devtools/blob/master/docs/workaround-for-firefox.md)

[Workaround for Safari](https://github.com/vuejs/vue-devtools/blob/master/docs/workaround-for-safari.md)

### Manual Installation

**Make sure you are using Node 6+ and NPM 3+**
Expand Down
10 changes: 10 additions & 0 deletions docs/workaround-for-safari.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#### Workaround to get **vue-devtools** in Safari.

1. Clone this repo
2. `npm install` (Or `yarn install` if you are using yarn as the package manager)
3. `npm run build:safari`
4. Open Safari preferences -> Advanced -> Show Develop menu in menu bar
5. Open Develop -> Show Extension Builder
6. Click the plus button in the bottom left and select Add Extension...
7. Select shells/safari/Vue.js devtools.safariextension
8. Click Install in the top right
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
"scripts": {
"dev": "cd shells/dev && webpack-dev-server --inline --hot --no-info",
"dev:chrome": "cd shells/chrome && webpack --watch --hide-modules",
"lint": "eslint src --ext=js,vue && eslint shells/chrome/src && eslint shells/dev/src",
"dev:safari": "cd shells/safari && webpack --watch --hide-modules",
"lint": "eslint src --ext=js,vue && eslint shells/chrome/src && eslint shells/dev/src && eslint shells/safari/src",
"build": "cd shells/chrome && cross-env NODE_ENV=production webpack --progress --hide-modules",
"build:safari": "cd shells/safari && NODE_ENV=production webpack --progress --hide-modules -p",
"zip": "npm run zip:chrome && npm run zip:firefox",
"zip:chrome": "cd shells && zip -r -FS ../dist/chrome.zip chrome -x *src/* -x *webpack.config.js",
"zip:firefox": "web-ext build -s shells/chrome -a dist -i src",
Expand Down
88 changes: 88 additions & 0 deletions shells/safari/Vue.js devtools.safariextension/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Author</key>
<string>Jared Hobbs</string>
<key>Builder Version</key>
<string>13604.1.21.7</string>
<key>CFBundleDisplayName</key>
<string>Vue.js devtools</string>
<key>CFBundleIdentifier</key>
<string>com.pyhacker.vuejsdevtools</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>Chrome</key>
<dict>
<key>Popovers</key>
<array>
<dict>
<key>Filename</key>
<string>devtools.html</string>
<key>Height</key>
<integer>600</integer>
<key>Identifier</key>
<string>VuePopover</string>
<key>Width</key>
<integer>800</integer>
</dict>
</array>
<key>Toolbar Items</key>
<array>
<dict>
<key>Identifier</key>
<string>vue-button</string>
<key>Image</key>
<string>icons/16-gray.png</string>
<key>Include By Default</key>
<true/>
<key>Label</key>
<string>Vue.js</string>
<key>Palette Label</key>
<string>Vue.js devtools</string>
<key>Popover</key>
<string>VuePopover</string>
<key>Tool Tip</key>
<string>Vue.js devtools</string>
</dict>
</array>
</dict>
<key>Content</key>
<dict>
<key>Scripts</key>
<dict>
<key>End</key>
<array>
<string>build/proxy.js</string>
</array>
<key>Start</key>
<array>
<string>build/hook-loader.js</string>
</array>
</dict>
</dict>
<key>Description</key>
<string>Safari extension for debugging Vue.js applications.</string>
<key>DeveloperIdentifier</key>
<string>0000000000</string>
<key>ExtensionInfoDictionaryVersion</key>
<string>1.0</string>
<key>Permissions</key>
<dict>
<key>Website Access</key>
<dict>
<key>Allowed Domains</key>
<array>
<string>localhost</string>
<string>127.0.0.1</string>
</array>
<key>Level</key>
<string>Some</string>
</dict>
</dict>
</dict>
</plist>
5 changes: 5 additions & 0 deletions shells/safari/Vue.js devtools.safariextension/Settings.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array/>
</plist>
22 changes: 22 additions & 0 deletions shells/safari/Vue.js devtools.safariextension/devtools.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<style>
html, body {
height: 100%;
}
#container {
display: flex;
height: 100%;
}
</style>
</head>
<body>
<div id="container">
<div id="app"></div>
</div>
<script src="./build/devtools.js"></script>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions shells/safari/src/backend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// this is injected to the app page when the panel is activated.

import { initBackend } from 'src/backend'
import Bridge from 'src/bridge'

let listeners = []
const bridge = new Bridge({
listen (fn) {
const listener = evt => {
if (evt.data.source === 'vue-devtools-proxy' && evt.data.payload) {
fn(evt.data.payload)
}
}
window.addEventListener('message', listener)
listeners.push(listener)
},
send (data) {
window.postMessage({
source: 'vue-devtools-backend',
payload: data
}, '*')
}
})

bridge.on('shutdown', () => {
console.log('shutdown')
listeners.forEach(l => {
window.removeEventListener('message', l)
})
listeners = []
})

initBackend(bridge)
55 changes: 55 additions & 0 deletions shells/safari/src/devtools.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*global safari*/
import { initDevTools } from 'src/devtools'
import Bridge from 'src/bridge'

// 1. load user app
// 2. init devtools when extention bar button is clicked
safari.application.addEventListener('popover', (evt) => {
if (evt.target.identifier !== 'VuePopover') {
return
}
initDevTools({
connect (cb) {
// 3. called by devtools: inject backend
inject(`${safari.extension.baseURI}build/backend.js`, () => {
// 4. send back bridge
cb(new Bridge({
listen (fn) {
safari.application.addEventListener('message', evt => {
if (evt.message.source === 'vue-devtools-proxy') {
fn(evt.message.payload)
}
}, false)
},
send (data) {
safari.application.activeBrowserWindow.activeTab.page.dispatchMessage('send', data)
}
}))
})
},
onReload () {
console.log('[devtools] reloaded')
}
})
}, true)

const callbacks = {}
function inject (scriptName, done) {
const src = `
var script = document.constructor.prototype.createElement.call(document, 'script');
script.src = "${scriptName}";
document.documentElement.appendChild(script);
script.parentNode.removeChild(script);
`
callbacks[src] = done
safari.application.activeBrowserWindow.activeTab.page.dispatchMessage('inject', src)
}

function runCallback (evt) {
if (evt.name === 'script-loaded') {
callbacks[evt.message]()
safari.application.removeEventListener('message', runCallback)
}
}

safari.application.addEventListener('message', runCallback)
5 changes: 5 additions & 0 deletions shells/safari/src/hook-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/*global safari*/
const script = document.constructor.prototype.createElement.call(document, 'script')
script.src = `${safari.extension.baseURI}build/hook.js`
document.documentElement.appendChild(script)
script.parentNode.removeChild(script)
3 changes: 3 additions & 0 deletions shells/safari/src/hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { installHook } from 'src/backend/hook'

installHook(window)
22 changes: 22 additions & 0 deletions shells/safari/src/proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*global safari*/
function handleMessage (evt) {
const name = evt.name
const data = evt.message
if (name === 'inject') {
eval(data) // eslint-disable-line no-eval
safari.self.tab.dispatchMessage('script-loaded', data)
} else if (name === 'send') {
const script = document.constructor.prototype.createElement.call(document, 'script')
const msg = JSON.stringify({ source: 'vue-devtools-proxy', payload: data })
script.innerHTML = `window.postMessage(${msg}, '*');`
document.documentElement.appendChild(script)
script.parentNode.removeChild(script)
}
}

safari.self.addEventListener('message', handleMessage, false)
window.addEventListener('message', (evt) => {
if (evt.data.source === 'vue-devtools-backend' && evt.data.payload) {
safari.self.tab.dispatchMessage('send', { source: 'vue-devtools-proxy', payload: evt.data.payload })
}
})
61 changes: 61 additions & 0 deletions shells/safari/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
var path = require('path')
var webpack = require('webpack')
var alias = require('../alias')
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')

var bubleOptions = {
target: { safari: 9 },
objectAssign: 'Object.assign'
}

module.exports = {
entry: {
devtools: './src/devtools.js',
backend: './src/backend.js',
hook: './src/hook.js',
'hook-loader': './src/hook-loader.js',
proxy: './src/proxy.js',
},
output: {
path: __dirname + '/Vue.js devtools.safariextension/build',
publicPath: '/build/',
filename: '[name].js',
},
resolve: {
alias: Object.assign({}, alias, {
vue$: 'vue/dist/vue.common.js'
})
},
module: {
rules: [
{
test: /\.js$/,
loader: 'buble-loader',
exclude: /node_modules|vue\/dist|vuex\/dist/,
options: bubleOptions
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
preserveWhitespace: false,
buble: bubleOptions
}
},
{
test: /\.(png|woff2)$/,
loader: 'url-loader?limit=0'
}
]
},
performance: {
hints: false
},
devtool: '#cheap-module-eval-source-map',
devServer: {
quiet: true
},
plugins: [
new FriendlyErrorsPlugin()
]
}
13 changes: 9 additions & 4 deletions src/bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import { EventEmitter } from 'events'
export default class Bridge extends EventEmitter {
constructor (wall) {
super()
this.setMaxListeners(Infinity)
this.wall = wall
// Setting `this` to `self` here to fix an error in the Safari build:
// ReferenceError: Cannot access uninitialized variable.
// The error might be related to the webkit bug here:
// https://bugs.webkit.org/show_bug.cgi?id=171543
const self = this
self.setMaxListeners(Infinity)
self.wall = wall
wall.listen(message => {
if (typeof message === 'string') {
this.emit(message)
self.emit(message)
} else {
this.emit(message.event, message.payload)
self.emit(message.event, message.payload)
}
})
}
Expand Down

0 comments on commit 4246f81

Please sign in to comment.