├── .babelrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── README.md ├── app ├── App.vue ├── assets │ └── logo.png ├── background.js ├── components │ └── Hello.vue ├── main.html ├── main.js └── package.json ├── build ├── css-loaders.js ├── dev-client.js ├── dev-runner.js ├── dev-server.js ├── package.js ├── resources │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── webpack.base.conf.js ├── webpack.dev-background.conf.js ├── webpack.dev-main.conf.js └── webpack.prod.conf.js ├── config.js ├── package.json ├── static ├── .gitkeep └── electron_boilerplate │ ├── context_menu.js │ ├── dev_helper.js │ ├── env_config.js │ ├── external_links.js │ └── window_state.js ├── test ├── e2e │ ├── custom-assertions │ │ └── elementCount.js │ ├── nightwatch.conf.js │ ├── runner.js │ └── specs │ │ └── test.js └── unit │ ├── .eslintrc │ ├── index.js │ ├── karma.conf.js │ └── specs │ └── Hello.spec.js └── tools └── vue-devtools ├── backend.js ├── devtools.js ├── hook.js └── index.html /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.json] 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 4 | extends: 'standard', 5 | // required to lint *.vue files 6 | plugins: [ 7 | 'html' 8 | ], 9 | // add your custom rules here 10 | 'rules': { 11 | // allow paren-less arrow functions 12 | 'arrow-parens': 0, 13 | // allow debugger during development 14 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 15 | }, 16 | 'env': { 17 | 'browser': true, 18 | 'node': true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | node_modules/ 4 | app/dist/ 5 | dist/ 6 | releases/ 7 | npm-debug.log* 8 | selenium-debug.log 9 | test/unit/coverage 10 | test/e2e/reports 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | electron-boilerplate-vue 2 | ============== 3 | Comprehensive boilerplate application for [Electron runtime](http://electron.atom.io) and [Vue.js](http://vuejs.org). This project is based on [vue-templates/webpack](https://github.com/vuejs-templates/webpack) and the awesome work by @szwacz on [electron-boilerplate](https://github.com/szwacz/electron-boilerplate). 4 | 5 | **This README is a work in progress.** This version is a large departure from the previous iteration. If you're looking for the old version, see [this branch](https://github.com/bradstewart/electron-boilerplate-vue/tree/legacy). 6 | 7 | Scope of this project: 8 | 9 | - Provide basic structure of the application so you can much easier grasp what should go where. 10 | - Give you cross-platform development environment, which works the same way on OSX, Windows and Linux. 11 | - Build scripts for the application using .vue components for production and development with Hot Module Reload. 12 | - Package scripts to generate executable files (.app, .exe). 13 | - Test scripts for both unit and end-to-end testing. 14 | - Generate ready for distribution installers of your app for all supported operating systems. 15 | 16 | Note: Installer generation is currently NOT implemented. Once electron-builder stablizes, I will add that to the project. 17 | 18 | 19 | # Quick start 20 | The only development dependency of this project is [Node.js](https://nodejs.org). So just make sure you have it installed. 21 | Then type few commands known to every Node developer... 22 | ``` 23 | git clone https://github.com/bradstewart/electron-boilerplate-vue.git 24 | cd electron-boilerplate-vue 25 | npm install 26 | npm start 27 | ``` 28 | ... and boom! You have running desktop application on your screen. 29 | 30 | # Structure of the project 31 | 32 | There are **two** `package.json` files: 33 | 34 | #### 1. For development 35 | Sits on path: `electron-boilerplate-vue/package.json`. Here you declare dependencies for your development environment and build scripts. **This file is not distributed with real application!** 36 | 37 | Also here you declare the version of Electron runtime you want to use: 38 | ```json 39 | "devDependencies": { 40 | "electron-prebuilt": "^0.34.0" 41 | } 42 | ``` 43 | 44 | #### 2. For your application 45 | Sits on path: `electron-boilerplate-vue/app/package.json`. This is **real** manifest of your application. Declare your app dependencies here. 46 | 47 | #### OMG, but seriously why there are two `package.json`? 48 | 1. Native npm modules (those written in C, not JavaScript) need to be compiled, and here we have two different compilation targets for them. Those used in application need to be compiled against electron runtime, and all `devDependencies` need to be compiled against your locally installed node.js. Thanks to having two files this is trivial. 49 | 2. When you package the app for distribution there is no need to add up to size of the app with your `devDependencies`. Here those are always not included (because reside outside the `app` directory). 50 | 51 | ### Project's folders 52 | 53 | - `app` - code of your application goes here. 54 | - `static` - static files which are not processed by webpack. 55 | - `build` - build scripts, configuration files, and resources. 56 | - `dist` - webpacked and runnable Electron app. 57 | - `releases` - ready for distribution installers and executables. 58 | - `test` - test configuration, runners, and specs. 59 | 60 | 61 | # Development 62 | 63 | #### Installation 64 | 65 | ``` 66 | npm install 67 | ``` 68 | It will also download Electron runtime, and install dependencies for second `package.json` file inside `app` folder. 69 | 70 | #### Starting the app 71 | 72 | ``` 73 | npm start 74 | ``` 75 | 76 | #### Adding pure-js npm modules to your app 77 | 78 | Remember to add your dependency to `app/package.json` file, so do: 79 | ``` 80 | cd app 81 | npm install name_of_npm_module --save 82 | ``` 83 | 84 | 85 | # Making a release 86 | 87 | **Note:** There are various icon and bitmap files in the `build/resources` directory. Those are used in installers and are intended to be replaced by your own graphics. 88 | 89 | To make ready for distribution installer use command: 90 | ``` 91 | npm run release 92 | ``` 93 | This process uses [electron-packager](https://github.com/electron-userland/electron-packager). See their documentation on packaging for various operating systems. 94 | 95 | 96 | # License 97 | 98 | The MIT License (MIT) 99 | 100 | Copyright (c) 2015 Jakub Szwacz, 2016 Brad Stewart 101 | 102 | Permission is hereby granted, free of charge, to any person obtaining a copy 103 | of this software and associated documentation files (the "Software"), to deal 104 | in the Software without restriction, including without limitation the rights 105 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 106 | copies of the Software, and to permit persons to whom the Software is 107 | furnished to do so, subject to the following conditions: 108 | 109 | The above copyright notice and this permission notice shall be included in all 110 | copies or substantial portions of the Software. 111 | 112 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 113 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 114 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 115 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 116 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 117 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 118 | SOFTWARE. 119 | -------------------------------------------------------------------------------- /app/App.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 32 | 33 | -------------------------------------------------------------------------------- /app/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradstewart/electron-boilerplate-vue/87afc09847431644cb86a5a14c52a0faa5007b4f/app/assets/logo.png -------------------------------------------------------------------------------- /app/background.js: -------------------------------------------------------------------------------- 1 | // This is main process of Electron, started as first thing when your 2 | // app starts. This script is running through entire life of your application. 3 | // It doesn't have any windows which you can see on screen, but we can open 4 | // window from here. 5 | 6 | import { app, BrowserWindow } from 'electron' 7 | import path from 'path' 8 | 9 | let mainWindow 10 | 11 | app.on('ready', () => { 12 | mainWindow = new BrowserWindow({ 13 | width: 1024, 14 | height: 768 15 | }) 16 | 17 | // Load the HTML file directly from the webpack dev server if 18 | // hot reload is enabled, otherwise load the local file. 19 | const mainURL = process.env.HOT 20 | ? `http://localhost:${process.env.PORT}/main.html` 21 | : 'file://' + path.join(__dirname, 'main.html') 22 | 23 | mainWindow.loadURL(mainURL) 24 | 25 | if (process.env.NODE_ENV !== 'production') { 26 | mainWindow.openDevTools() 27 | } 28 | 29 | mainWindow.on('closed', () => { 30 | mainWindow = null 31 | }) 32 | }) 33 | 34 | app.on('window-all-closed', () => { 35 | app.quit() 36 | }) 37 | -------------------------------------------------------------------------------- /app/components/Hello.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | -------------------------------------------------------------------------------- /app/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Electron Boilerplate Vue 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | 4 | /* eslint-disable no-new */ 5 | new Vue({ 6 | el: 'body', 7 | components: { App } 8 | }) 9 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-boilerplate-vue", 3 | "productName": "Electron Boilerplate Vue", 4 | "version": "0.1.0", 5 | "description": "An Electron + Vue.js project.", 6 | "author": "Brad Stewart ", 7 | "main": "background.js", 8 | "dependencies": { 9 | "jquery": "2.2.2" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /build/css-loaders.js: -------------------------------------------------------------------------------- 1 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 2 | 3 | module.exports = function (options) { 4 | options = options || {} 5 | // generate loader string to be used with extract text plugin 6 | function generateLoaders (loaders) { 7 | var sourceLoader = loaders.map(function (loader) { 8 | var extraParamChar 9 | if (/\?/.test(loader)) { 10 | loader = loader.replace(/\?/, '-loader?') 11 | extraParamChar = '&' 12 | } else { 13 | loader = loader + '-loader' 14 | extraParamChar = '?' 15 | } 16 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 17 | }).join('!') 18 | 19 | if (options.extract) { 20 | return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) 21 | } else { 22 | return ['vue-style-loader', sourceLoader].join('!') 23 | } 24 | } 25 | 26 | // http://vuejs.github.io/vue-loader/configurations/extract-css.html 27 | return { 28 | css: generateLoaders(['css']), 29 | postcss: generateLoaders(['css']), 30 | less: generateLoaders(['css', 'less']), 31 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 32 | scss: generateLoaders(['css', 'sass']), 33 | stylus: generateLoaders(['css', 'stylus']), 34 | styl: generateLoaders(['css', 'stylus']) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* global __resourceQuery */ 2 | 3 | // We need to explicity set the client's path to http://localhost:PORT 4 | // for it to work with Electron. In order to read the port from config.js, 5 | // we need to supply it as a compile-time constant through resourceQuery. 6 | var hotClient = require('webpack-hot-middleware/client' + __resourceQuery) 7 | // var hotClient = require('webpack-hot-middleware/client') 8 | 9 | hotClient.subscribe(function (event) { 10 | if (event.action === 'reload') { 11 | window.location.reload() 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /build/dev-runner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Runs both the express dev server and the electron client with 3 | * a single command. When one process exits, all processes exit. 4 | * Stdout and stderr from both processes is logged to the same console. 5 | */ 6 | var exec = require('child_process').exec 7 | var config = require('../config') 8 | 9 | var kill = require('tree-kill') 10 | 11 | var YELLOW = '\x1b[33m' 12 | var BLUE = '\x1b[34m' 13 | var END = '\x1b[0m' 14 | 15 | function format (command, data, color) { 16 | return color + command + END + 17 | ' ' + // Two space offset 18 | String(data).trim().replace(/\n/g, '\n' + repeat(' ', command.length + 2)) + 19 | '\n' 20 | } 21 | 22 | function repeat (str, times) { 23 | return (new Array(times + 1)).join(str) 24 | } 25 | 26 | var children = [] 27 | 28 | function run (command, color) { 29 | var child = exec(command) 30 | 31 | child.stdout.on('data', function (data) { 32 | console.log(format(command.split(' ')[2], data, color)) 33 | }) 34 | 35 | child.stderr.on('data', function (data) { 36 | console.error(format(command.split(' ')[2], data, color)) 37 | }) 38 | 39 | child.on('exit', function (code) { 40 | exit(code) 41 | }) 42 | 43 | children.push(child) 44 | } 45 | 46 | function exit (code) { 47 | children.forEach(function (child) { 48 | kill(child.pid) 49 | }) 50 | } 51 | 52 | // Run the client and the server 53 | run('npm run dev:server', YELLOW) 54 | run('npm run dev:client -- ' + config.build.outputRoot, BLUE) 55 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var webpack = require('webpack') 3 | var proxyMiddleware = require('http-proxy-middleware') 4 | var webpackConfig = require('./webpack.dev-main.conf') 5 | var config = require('../config') 6 | 7 | var app = express() 8 | var compiler = webpack(webpackConfig) 9 | 10 | var port = process.env.PORT || config.dev.port 11 | // Define HTTP proxies to your custom API backend 12 | // https://github.com/chimurai/http-proxy-middleware 13 | var proxyTable = config.dev.proxyTable 14 | 15 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 16 | publicPath: webpackConfig.output.publicPath, 17 | stats: { 18 | colors: true, 19 | chunks: false 20 | } 21 | }) 22 | 23 | var hotMiddleware = require('webpack-hot-middleware')(compiler) 24 | // force page reload when html-webpack-plugin template changes 25 | compiler.plugin('compilation', function (compilation) { 26 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 27 | hotMiddleware.publish({ action: 'reload' }) 28 | cb() 29 | }) 30 | }) 31 | 32 | // proxy api requests 33 | Object.keys(proxyTable).forEach(function (context) { 34 | var options = proxyTable[context] 35 | if (typeof options === 'string') { 36 | options = { target: options } 37 | } 38 | app.use(proxyMiddleware(context, options)) 39 | }) 40 | 41 | // handle fallback for HTML5 history API 42 | app.use(require('connect-history-api-fallback')()) 43 | 44 | // serve webpack bundle output 45 | app.use(devMiddleware) 46 | 47 | // enable hot-reload and state-preserving 48 | // compilation error display 49 | app.use(hotMiddleware) 50 | 51 | // serve pure static assets 52 | app.use('/static', express.static('./static')) 53 | 54 | module.exports = app.listen(port, function (err) { 55 | if (err) { 56 | console.log(err) 57 | return 58 | } 59 | console.log('Listening at http://localhost:' + port) 60 | }) 61 | -------------------------------------------------------------------------------- /build/package.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Packages the application into executable .app and .exe files. 3 | * For more info, see https://github.com/electron-userland/electron-packager. 4 | */ 5 | var argv = require('minimist')(process.argv.slice(2)) 6 | var packager = require('electron-packager') 7 | var appManifest = require('../app/package.json') 8 | var devManifest = require('../package.json') 9 | var config = require('../config') 10 | 11 | function getElectronVersion () { 12 | var v = config.release.electronVersion || 13 | (devManifest.devDependencies || {})['electron-prebuilt'] || 14 | (devManifest.dependencies || {})['electron-prebuilt'] 15 | 16 | if (v) { 17 | return v.replace(/^\D+/, '') 18 | } else { 19 | console.log( 20 | 'No electron version was found in config.js or package.json.' 21 | ) 22 | } 23 | } 24 | 25 | var packagerConfig = { 26 | dir: config.build.outputRoot, 27 | out: config.build.releasesRoot, 28 | name: appManifest.productName, 29 | 'app-version': appManifest.version, 30 | version: getElectronVersion(), 31 | platform: argv.platform || config.release.platform, 32 | arch: argv.arch || 'all', 33 | prune: true, 34 | overwrite: true, 35 | ignore: Object.keys((appManifest.devDependencies || {})).map(function (name) { 36 | return '/node_modules/' + name + '($|/)' 37 | }) 38 | } 39 | 40 | packager(packagerConfig, function (err, appPath) { 41 | if (err) { 42 | console.error(err) 43 | process.exit(1) 44 | } 45 | 46 | console.log('packaged to ' + appPath) 47 | }) 48 | -------------------------------------------------------------------------------- /build/resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradstewart/electron-boilerplate-vue/87afc09847431644cb86a5a14c52a0faa5007b4f/build/resources/icon.icns -------------------------------------------------------------------------------- /build/resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradstewart/electron-boilerplate-vue/87afc09847431644cb86a5a14c52a0faa5007b4f/build/resources/icon.ico -------------------------------------------------------------------------------- /build/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradstewart/electron-boilerplate-vue/87afc09847431644cb86a5a14c52a0faa5007b4f/build/resources/icon.png -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var cssLoaders = require('./css-loaders') 5 | var projectRoot = path.resolve(__dirname, '../') 6 | 7 | module.exports = { 8 | // Note: entry points are added by environment-specific configs. 9 | 10 | output: { 11 | path: config.build.outputRoot, 12 | filename: '[name].js' 13 | }, 14 | // Use target 'node' so that __dirname works properly. We then need 15 | // to manually specify the electron modules in the ExternalsPlugin 16 | // since we're not using target 'electron'. 17 | target: 'node', 18 | node: { 19 | __filename: false, 20 | __dirname: false 21 | }, 22 | resolve: { 23 | extensions: ['', '.js', '.vue'], 24 | fallback: [path.join(__dirname, '../node_modules')], 25 | alias: { 26 | app: path.resolve(__dirname, '../app') 27 | } 28 | }, 29 | resolveLoader: { 30 | fallback: [path.join(__dirname, '../node_modules')] 31 | }, 32 | module: { 33 | preLoaders: [ 34 | { 35 | test: /\.vue$/, 36 | loader: 'eslint', 37 | include: projectRoot, 38 | exclude: /node_modules/ 39 | }, 40 | { 41 | test: /\.js$/, 42 | loader: 'eslint', 43 | include: projectRoot, 44 | exclude: /vue-devtools|node_modules/ 45 | } 46 | ], 47 | loaders: [ 48 | { 49 | test: /\.vue$/, 50 | loader: 'vue' 51 | }, 52 | { 53 | test: /\.js$/, 54 | loader: 'babel', 55 | include: projectRoot, 56 | exclude: /vue-devtools|node_modules/ 57 | }, 58 | { 59 | test: /\.json$/, 60 | loader: 'json' 61 | }, 62 | { 63 | test: /\.html$/, 64 | loader: 'vue-html' 65 | }, 66 | { 67 | test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf|otf)(\?.*)?$/, 68 | loader: 'url', 69 | query: { 70 | limit: 10000, 71 | name: path.join(config.build.assetsSubDirectory, '[name].[ext]').replace('\\', '/') 72 | } 73 | } 74 | ] 75 | }, 76 | plugins: [ 77 | new webpack.ExternalsPlugin('commonjs2', [ 78 | 'desktop-capturer', 79 | 'electron', 80 | 'ipc', 81 | 'ipc-renderer', 82 | 'native-image', 83 | 'remote', 84 | 'web-frame', 85 | 'clipboard', 86 | 'crash-reporter', 87 | 'screen', 88 | 'shell' 89 | ]) 90 | ], 91 | vue: { 92 | loaders: cssLoaders() 93 | }, 94 | eslint: { 95 | formatter: require('eslint-friendly-formatter') 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /build/webpack.dev-background.conf.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var merge = require('webpack-merge') 3 | var CopyWebpackPlugin = require('copy-webpack-plugin') 4 | var webpackBaseConfig = require('./webpack.base.conf') 5 | var config = require('../config') 6 | 7 | // var cssLoaders = require('./css-loaders') 8 | // var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | 10 | // add hot-reload related code to entry chunks 11 | // Object.keys(webpackBaseConfig.entry).forEach(function (name) { 12 | // webpackBaseConfig.entry[name] = ['./build/dev-client'].concat(webpackBaseConfig.entry[name]) 13 | // }) 14 | 15 | module.exports = merge(webpackBaseConfig, { 16 | entry: { 17 | background: './app/background.js' 18 | }, 19 | // eval-source-map is faster for development 20 | devtool: '#eval-source-map', 21 | output: { 22 | // necessary for the html plugin to work properly 23 | // when serving the html from in-memory 24 | publicPath: '/' 25 | }, 26 | plugins: [ 27 | // Copy files from app to dist 28 | new CopyWebpackPlugin([ 29 | { from: './app/package.json', to: '.'} 30 | ]), 31 | 32 | new webpack.DefinePlugin({ 33 | 'process.env': { 34 | NODE_ENV: '"development"', 35 | HOT: JSON.stringify(process.env.HOT), 36 | PORT: JSON.stringify(process.env.PORT || config.dev.port) 37 | } 38 | }), 39 | 40 | new webpack.optimize.OccurenceOrderPlugin(), 41 | new webpack.NoErrorsPlugin() 42 | ], 43 | stats: { 44 | colors: true 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /build/webpack.dev-main.conf.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var merge = require('webpack-merge') 3 | var HtmlWebpackPlugin = require('html-webpack-plugin') 4 | var webpackBaseConfig = require('./webpack.base.conf') 5 | var config = require('../config') 6 | var cssLoaders = require('./css-loaders') 7 | 8 | var devServerUrl = 'http://localhost:' + config.dev.port + '/' 9 | 10 | var webpackConfig = merge(webpackBaseConfig, { 11 | entry: { 12 | app: [ 13 | './build/dev-client?path=' + devServerUrl + '__webpack_hmr&noInfo=true&reload=true', 14 | './app/main.js' 15 | ] 16 | }, 17 | // eval-source-map is faster for development 18 | devtool: '#eval-source-map', 19 | output: { 20 | // necessary for the html plugin to work properly 21 | // when serving the html from in-memory 22 | // need to explicitly set localhost to prevent 23 | // the hot updates from looking for local files 24 | publicPath: devServerUrl 25 | }, 26 | vue: { 27 | loaders: cssLoaders({ 28 | sourceMap: false, 29 | extract: false 30 | }) 31 | }, 32 | plugins: [ 33 | new webpack.DefinePlugin({ 34 | 'process.env': { 35 | NODE_ENV: '"development"' 36 | } 37 | }), 38 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 39 | new webpack.optimize.OccurenceOrderPlugin(), 40 | new webpack.HotModuleReplacementPlugin(), 41 | new webpack.NoErrorsPlugin(), 42 | // https://github.com/ampedandwired/html-webpack-plugin 43 | new HtmlWebpackPlugin({ 44 | filename: 'main.html', 45 | template: './app/main.html', 46 | excludeChunks: ['devtools'], 47 | inject: true 48 | }) 49 | ] 50 | }) 51 | 52 | if (config.dev.vueDevTools) { 53 | webpackConfig.entry.app.unshift( 54 | './tools/vue-devtools/hook.js', 55 | './tools/vue-devtools/backend.js' 56 | ) 57 | webpackConfig.entry.devtools = './tools/vue-devtools/devtools.js' 58 | 59 | webpackConfig.plugins.push(new HtmlWebpackPlugin({ 60 | filename: 'devtools.html', 61 | template: './tools/vue-devtools/index.html', 62 | chunks: ['devtools'], 63 | inject: true 64 | })) 65 | } 66 | 67 | module.exports = webpackConfig 68 | 69 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | var merge = require('webpack-merge') 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | var HtmlWebpackPlugin = require('html-webpack-plugin') 6 | var CopyWebpackPlugin = require('copy-webpack-plugin') 7 | var config = require('../config') 8 | var baseWebpackConfig = require('./webpack.base.conf') 9 | var cssLoaders = require('./css-loaders') 10 | 11 | module.exports = merge(baseWebpackConfig, { 12 | entry: { 13 | app: './app/main.js', 14 | background: './app/background.js' 15 | }, 16 | devtool: config.build.productionSourceMap ? '#source-map' : false, 17 | output: { 18 | filename: '[name].js', 19 | chunkFilename: '[id].js' 20 | }, 21 | vue: { 22 | loaders: cssLoaders({ 23 | sourceMap: config.build.productionSourceMap, 24 | extract: true 25 | }) 26 | }, 27 | plugins: [ 28 | new CopyWebpackPlugin([ 29 | { from: './app/package.json', to: '.' }, 30 | { from: './static', to: 'static' } 31 | ]), 32 | // http://vuejs.github.io/vue-loader/workflow/production.html 33 | new webpack.DefinePlugin({ 34 | 'process.env': { 35 | NODE_ENV: '"production"' 36 | } 37 | }), 38 | new webpack.optimize.UglifyJsPlugin({ 39 | compress: { 40 | warnings: false 41 | } 42 | }), 43 | new webpack.optimize.OccurenceOrderPlugin(), 44 | // extract css into its own file 45 | new ExtractTextPlugin(path.join(config.build.assetsSubDirectory, '[name].css')), 46 | // generate dist index.html with correct asset hash for caching. 47 | // you can customize output by editing /index.html 48 | // see https://github.com/ampedandwired/html-webpack-plugin 49 | new HtmlWebpackPlugin({ 50 | filename: 'main.html', 51 | template: './app/main.html', 52 | excludeChunks: ['background'], 53 | inject: true, 54 | minify: { 55 | removeComments: true, 56 | collapseWhitespace: true, 57 | removeAttributeQuotes: true 58 | // more options: 59 | // https://github.com/kangax/html-minifier#options-quick-reference 60 | } 61 | }) 62 | ] 63 | }) 64 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * General configuration for build and release scripts. 3 | * 4 | * This file as adapted from http://vuejs-templates.github.io/webpack. 5 | * Some options were removed or changed to simplify things and/or 6 | * make them more applicable to Electron apps. 7 | */ 8 | var path = require('path') 9 | 10 | module.exports = { 11 | build: { 12 | // The directory which will contained packaged releases and installers 13 | // for various operation systems. 14 | releasesRoot: path.resolve(__dirname, 'releases'), 15 | 16 | // The target directory for your app's compiled assets. Must be an absolute path. 17 | // This is the directory which will contain a runnable electron app. 18 | outputRoot: path.resolve(__dirname, 'dist'), 19 | 20 | // Nest webpack-generated assets under this directory in `assetsRoot`. 21 | // This applies to all non-JavaScript assets processed by webpack. 22 | assetsSubDirectory: 'assets', 23 | 24 | // Whether to generate source maps for production builds. 25 | productionSourceMap: true 26 | }, 27 | release: { 28 | // The Electron version to use for packaged releases. If blank, it defaults 29 | // to the version of electron-prebuilt in your development package.json. 30 | // 31 | // electronVersion: '0.37.2', 32 | 33 | // The target platforms for packaged releases. For options, see 34 | // https://github.com/electron-userland/electron-packager 35 | platform: 'all' 36 | }, 37 | dev: { 38 | // Dev server port. 39 | port: 8080, 40 | 41 | // Proxy requests to different backend during development. 42 | // https://github.com/chimurai/http-proxy-middleware 43 | proxyTable: { 44 | // '/api': { 45 | // target: 'http://jsonplaceholder.typicode.com', 46 | // changeOrigin: true, 47 | // pathRewrite: { 48 | // '^/api': '' 49 | // } 50 | // } 51 | }, 52 | 53 | // Whether or not open another Electron window with vue-devtools. 54 | vueDevTools: true 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "postinstall": "cd app && npm install", 4 | "start": "node build/dev-runner.js", 5 | "dev:server": "node build/dev-server.js", 6 | "dev:client": "cross-env HOT=1 webpack --hide-modules --config build/webpack.dev-background.conf.js && cross-env HOT=1 electron", 7 | "build": "rimraf dist && mkdirp dist && cross-env NODE_ENV=production webpack --progress --hide-modules --config build/webpack.prod.conf.js", 8 | "package": "node build/package.js", 9 | "package:osx": "node build/package.js --platform=darwin --arch=x64", 10 | "package:win": "node build/package.js --platform=win32", 11 | "package:linux": "node build/package.js --platform=linux", 12 | "release": "npm run build && npm run package", 13 | "unit": "karma start test/unit/karma.conf.js --single-run", 14 | "e2e": "node test/e2e/runner.js", 15 | "test": "npm run unit && npm run e2e" 16 | }, 17 | "devDependencies": { 18 | "babel-core": "^6.0.0", 19 | "babel-loader": "^6.0.0", 20 | "babel-plugin-transform-runtime": "^6.0.0", 21 | "babel-preset-es2015": "^6.0.0", 22 | "babel-preset-stage-2": "^6.0.0", 23 | "babel-runtime": "^6.0.0", 24 | "chromedriver": "^2.21.2", 25 | "connect-history-api-fallback": "^1.1.0", 26 | "copy-webpack-plugin": "^1.1.1", 27 | "cross-env": "1.0.7", 28 | "cross-spawn": "^2.1.5", 29 | "css-loader": "^0.23.0", 30 | "debug-menu": "^0.3.0", 31 | "electron-packager": "^7.0.0", 32 | "electron-prebuilt": "1.1.0", 33 | "electron-rebuild": "^1.1.3", 34 | "eslint": "^2.0.0", 35 | "eslint-config-standard": "^5.1.0", 36 | "eslint-friendly-formatter": "^1.2.2", 37 | "eslint-loader": "^1.3.0", 38 | "eslint-plugin-html": "^1.3.0", 39 | "eslint-plugin-promise": "^1.0.8", 40 | "eslint-plugin-standard": "^1.3.2", 41 | "eventsource-polyfill": "^0.9.6", 42 | "express": "^4.13.3", 43 | "extract-text-webpack-plugin": "^1.0.1", 44 | "file-loader": "^0.8.4", 45 | "html-webpack-plugin": "^2.8.1", 46 | "http-proxy-middleware": "^0.11.0", 47 | "inject-loader": "^2.0.1", 48 | "isparta-loader": "^2.0.0", 49 | "jasmine-core": "^2.4.1", 50 | "json-loader": "^0.5.4", 51 | "karma": "^0.13.15", 52 | "karma-coverage": "^0.5.5", 53 | "karma-electron-launcher": "^0.1.0", 54 | "karma-jasmine": "^0.3.6", 55 | "karma-sourcemap-loader": "^0.3.7", 56 | "karma-spec-reporter": "^0.0.24", 57 | "karma-webpack": "^1.7.0", 58 | "mkdirp": "^0.5.1", 59 | "moment": "2.12.0", 60 | "ncp": "^2.0.0", 61 | "nightwatch": "^0.8.18", 62 | "phantomjs-prebuilt": "^2.1.3", 63 | "rimraf": "^2.5.0", 64 | "selenium-server": "2.52.0", 65 | "tree-kill": "^1.1.0", 66 | "url-loader": "^0.5.7", 67 | "vue": "^1.0.17", 68 | "vue-hot-reload-api": "^1.2.0", 69 | "vue-html-loader": "^1.0.0", 70 | "vue-loader": "^8.3.0", 71 | "vue-router": "^0.7.11", 72 | "vue-style-loader": "^1.0.0", 73 | "webpack": "^1.12.2", 74 | "webpack-dev-middleware": "^1.4.0", 75 | "webpack-externals-plugin": "1.0.0", 76 | "webpack-hot-middleware": "^2.6.0", 77 | "webpack-merge": "^0.8.3" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradstewart/electron-boilerplate-vue/87afc09847431644cb86a5a14c52a0faa5007b4f/static/.gitkeep -------------------------------------------------------------------------------- /static/electron_boilerplate/context_menu.js: -------------------------------------------------------------------------------- 1 | // This gives you default context menu (cut, copy, paste) 2 | // in all input fields and textareas across your app. 3 | 4 | (function () { 5 | 'use strict'; 6 | 7 | var remote = require('electron').remote; 8 | var Menu = remote.Menu; 9 | var MenuItem = remote.MenuItem; 10 | 11 | var cut = new MenuItem({ 12 | label: "Cut", 13 | click: function () { 14 | document.execCommand("cut"); 15 | } 16 | }); 17 | 18 | var copy = new MenuItem({ 19 | label: "Copy", 20 | click: function () { 21 | document.execCommand("copy"); 22 | } 23 | }); 24 | 25 | var paste = new MenuItem({ 26 | label: "Paste", 27 | click: function () { 28 | document.execCommand("paste"); 29 | } 30 | }); 31 | 32 | var textMenu = new Menu(); 33 | textMenu.append(cut); 34 | textMenu.append(copy); 35 | textMenu.append(paste); 36 | 37 | document.addEventListener('contextmenu', function(e) { 38 | 39 | switch (e.target.nodeName) { 40 | case 'TEXTAREA': 41 | case 'INPUT': 42 | e.preventDefault(); 43 | textMenu.popup(remote.getCurrentWindow()); 44 | break; 45 | } 46 | 47 | }, false); 48 | 49 | }()); 50 | -------------------------------------------------------------------------------- /static/electron_boilerplate/dev_helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var app = require('app'); 4 | var Menu = require('menu'); 5 | var BrowserWindow = require('browser-window'); 6 | 7 | module.exports.setDevMenu = function () { 8 | var devMenu = Menu.buildFromTemplate([{ 9 | label: 'Development', 10 | submenu: [{ 11 | label: 'Reload', 12 | accelerator: 'CmdOrCtrl+R', 13 | click: function () { 14 | BrowserWindow.getFocusedWindow().reloadIgnoringCache(); 15 | } 16 | },{ 17 | label: 'Toggle DevTools', 18 | accelerator: 'Alt+CmdOrCtrl+I', 19 | click: function () { 20 | BrowserWindow.getFocusedWindow().toggleDevTools(); 21 | } 22 | },{ 23 | label: 'Quit', 24 | accelerator: 'CmdOrCtrl+Q', 25 | click: function () { 26 | app.quit(); 27 | } 28 | }] 29 | }]); 30 | Menu.setApplicationMenu(devMenu); 31 | }; 32 | -------------------------------------------------------------------------------- /static/electron_boilerplate/env_config.js: -------------------------------------------------------------------------------- 1 | // Loads config/env_XXX.json file and puts it 2 | // in proper place for given Electron context. 3 | 4 | 'use strict'; 5 | 6 | (function () { 7 | var jetpack = require('fs-jetpack'); 8 | if (typeof window === 'object') { 9 | // Web browser context, __dirname points to folder where app.html file is. 10 | window.env = jetpack.read(__dirname + '/env_config.json', 'json'); 11 | } else { 12 | // Node context 13 | module.exports = jetpack.read(__dirname + '/../../env_config.json', 'json'); 14 | } 15 | }()); 16 | -------------------------------------------------------------------------------- /static/electron_boilerplate/external_links.js: -------------------------------------------------------------------------------- 1 | // Convenient way for opening links in external browser, not in the app. 2 | // Useful especially if you have a lot of links to deal with. 3 | // 4 | // Usage: 5 | // 6 | // Every link with class ".js-external-link" will be opened in external browser. 7 | // google 8 | // 9 | // The same behaviour for many links can be achieved by adding 10 | // this class to any parent tag of an anchor tag. 11 | // 15 | 16 | (function () { 17 | 'use strict'; 18 | var shell = require('electron').shell; 19 | 20 | var supportExternalLinks = function (e) { 21 | var href; 22 | var isExternal = false; 23 | 24 | var checkDomElement = function (element) { 25 | if (element.nodeName === 'A') { 26 | href = element.getAttribute('href'); 27 | } 28 | if (element.classList.contains('js-external-link')) { 29 | isExternal = true; 30 | } 31 | if (href && isExternal) { 32 | shell.openExternal(href); 33 | e.preventDefault(); 34 | } else if (element.parentElement) { 35 | checkDomElement(element.parentElement); 36 | } 37 | } 38 | 39 | checkDomElement(e.target); 40 | } 41 | 42 | document.addEventListener('click', supportExternalLinks, false); 43 | }()); 44 | -------------------------------------------------------------------------------- /static/electron_boilerplate/window_state.js: -------------------------------------------------------------------------------- 1 | // Simple module to help you remember the size and position of windows. 2 | // Can be used for more than one window, just construct many 3 | // instances of it and give each different name. 4 | 5 | 'use strict'; 6 | 7 | var app = require('app'); 8 | var jetpack = require('fs-jetpack'); 9 | 10 | module.exports = function (name, defaults) { 11 | 12 | var userDataDir = jetpack.cwd(app.getPath('userData')); 13 | var stateStoreFile = 'window-state-' + name +'.json'; 14 | 15 | var state = userDataDir.read(stateStoreFile, 'json') || { 16 | width: defaults.width, 17 | height: defaults.height 18 | }; 19 | 20 | var saveState = function (win) { 21 | if (!win.isMaximized() && !win.isMinimized()) { 22 | var position = win.getPosition(); 23 | var size = win.getSize(); 24 | state.x = position[0]; 25 | state.y = position[1]; 26 | state.width = size[0]; 27 | state.height = size[1]; 28 | } 29 | state.isMaximized = win.isMaximized(); 30 | userDataDir.write(stateStoreFile, state, { atomic: true }); 31 | }; 32 | 33 | return { 34 | get x() { return state.x; }, 35 | get y() { return state.y; }, 36 | get width() { return state.width; }, 37 | get height() { return state.height; }, 38 | get isMaximized() { return state.isMaximized; }, 39 | saveState: saveState 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // the name of the method is the filename. 3 | // can be used in tests like this: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // for how to write custom assertions see 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | exports.assertion = function (selector, count) { 10 | this.message = 'Testing if element <' + selector + '> has count: ' + count 11 | this.expected = count 12 | this.pass = function (val) { 13 | return val === this.expected 14 | } 15 | this.value = function (res) { 16 | return res.value 17 | } 18 | this.command = function (cb) { 19 | var self = this 20 | return this.api.execute(function (selector) { 21 | return document.querySelectorAll(selector).length 22 | }, [selector], function (res) { 23 | cb.call(self, res) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | // http://nightwatchjs.org/guide#settings-file 2 | 3 | module.exports = { 4 | 'src_folders': ['test/e2e/specs'], 5 | 'output_folder': 'test/e2e/reports', 6 | 'custom_assertions_path': ['test/e2e/custom-assertions'], 7 | 8 | 'selenium': { 9 | 'start_process': true, 10 | 'server_path': 'node_modules/selenium-server/lib/runner/selenium-server-standalone-2.52.0.jar', 11 | 'host': '127.0.0.1', 12 | 'port': 4444, 13 | 'cli_args': { 14 | 'webdriver.chrome.driver': require('chromedriver').path 15 | } 16 | }, 17 | 18 | 'test_settings': { 19 | 'default': { 20 | 'selenium_port': 4444, 21 | 'selenium_host': 'localhost', 22 | 'silent': true 23 | }, 24 | 25 | 'electron': { 26 | 'desiredCapabilities': { 27 | 'browserName': 'chrome', 28 | 'javascriptEnabled': true, 29 | 'acceptSslCerts': true, 30 | chromeOptions: { 31 | binary: require('electron-prebuilt') // Path to your Electron binary. 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server 2 | var server = require('../../build/dev-server.js') 3 | 4 | // 2. run the nightwatch test suite against it 5 | // to run in additional browsers: 6 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 7 | // 2. add it to the --env flag below 8 | // For more information on Nightwatch's config file, see 9 | // http://nightwatchjs.org/guide#settings-file 10 | var spawn = require('cross-spawn') 11 | var runner = spawn( 12 | './node_modules/.bin/nightwatch', 13 | [ 14 | '--config', 'test/e2e/nightwatch.conf.js', 15 | '--env', 'electron' 16 | ], 17 | { 18 | stdio: 'inherit' 19 | } 20 | ) 21 | 22 | runner.on('exit', function (code) { 23 | server.close() 24 | process.exit(code) 25 | }) 26 | 27 | runner.on('error', function (err) { 28 | server.close() 29 | throw err 30 | }) 31 | -------------------------------------------------------------------------------- /test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | browser 7 | .url('http://localhost:8080/main.html') 8 | .waitForElementVisible('#app', 5000) 9 | .assert.elementPresent('.logo') 10 | .assert.containsText('h1', 'Hello World') 11 | .assert.elementCount('p', 3) 12 | .end() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jasmine": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | // Add this to make Electron code run properly. To be perfectly 2 | // honest, I'm not sure why this is necessary. 3 | window.process = window.parent.process 4 | window.require = window.parent.require 5 | 6 | // require all test files (files that ends with .spec.js) 7 | var testsContext = require.context('./specs', true, /\.spec$/) 8 | testsContext.keys().forEach(testsContext) 9 | 10 | // require all src files except main.js for coverage. 11 | // you can also change this to match only the subset of files that 12 | // you want coverage for. 13 | var srcContext = require.context('../../app', true, /^\.\/(?!(background|main)(\.js)?$)/) 14 | srcContext.keys().forEach(srcContext) 15 | -------------------------------------------------------------------------------- /test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | var path = require('path') 7 | var merge = require('webpack-merge') 8 | var baseConfig = require('../../build/webpack.base.conf') 9 | var projectRoot = path.resolve(__dirname, '../../') 10 | 11 | var webpackConfig = merge(baseConfig, { 12 | // use inline sourcemap for karma-sourcemap-loader 13 | devtool: '#inline-source-map', 14 | vue: { 15 | loaders: { 16 | js: 'isparta' 17 | } 18 | } 19 | }) 20 | 21 | // make sure isparta loader is applied before eslint 22 | webpackConfig.module.preLoaders.unshift({ 23 | test: /\.js$/, 24 | loader: 'isparta', 25 | include: projectRoot, 26 | exclude: /test\/unit|node_modules/ 27 | }) 28 | 29 | // only apply babel for test files when using isparta 30 | webpackConfig.module.loaders.some(function (loader, i) { 31 | if (loader.loader === 'babel') { 32 | loader.include = /test\/unit/ 33 | return true 34 | } 35 | }) 36 | 37 | module.exports = function (config) { 38 | config.set({ 39 | // to run in additional browsers: 40 | // 1. install corresponding karma launcher 41 | // http://karma-runner.github.io/0.13/config/browsers.html 42 | // 2. add it to the `browsers` array below. 43 | browsers: ['Electron'], 44 | frameworks: ['jasmine'], 45 | reporters: ['spec', 'coverage'], 46 | files: ['./index.js'], 47 | preprocessors: { 48 | './index.js': ['webpack', 'sourcemap'] 49 | }, 50 | webpack: webpackConfig, 51 | webpackMiddleware: { 52 | noInfo: true 53 | }, 54 | coverageReporter: { 55 | dir: './coverage', 56 | reporters: [ 57 | { type: 'lcov', subdir: '.' }, 58 | { type: 'text-summary' } 59 | ] 60 | } 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /test/unit/specs/Hello.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Hello from 'app/components/Hello' 3 | 4 | describe('Hello.vue', () => { 5 | it('should render correct contents', () => { 6 | const vm = new Vue({ 7 | template: '
', 8 | components: { Hello } 9 | }).$mount() 10 | expect(vm.$el.querySelector('.hello h1').textContent).toBe('Hello World!') 11 | }) 12 | }) 13 | 14 | // also see example testing a component with mocks at 15 | // https://github.com/vuejs/vue-loader-example/blob/master/test/unit/a.spec.js#L24-L49 16 | -------------------------------------------------------------------------------- /tools/vue-devtools/hook.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) 10 | /******/ return installedModules[moduleId].exports; 11 | 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ exports: {}, 15 | /******/ id: moduleId, 16 | /******/ loaded: false 17 | /******/ }; 18 | 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | 22 | /******/ // Flag the module as loaded 23 | /******/ module.loaded = true; 24 | 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | 29 | 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | 36 | /******/ // __webpack_public_path__ 37 | /******/ __webpack_require__.p = "/build/"; 38 | 39 | /******/ // Load entry module and return exports 40 | /******/ return __webpack_require__(0); 41 | /******/ }) 42 | /************************************************************************/ 43 | /******/ ({ 44 | 45 | /***/ 0: 46 | /***/ function(module, exports, __webpack_require__) { 47 | 48 | eval("'use strict';\n\nvar _hook = __webpack_require__(162);\n\n(0, _hook.installHook)(window);\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9zcmMvaG9vay5qcz80YTAwIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUE7O0FBRUEsdUJBQVksTUFBWiIsImZpbGUiOiIwLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgaW5zdGFsbEhvb2sgfSBmcm9tICcuLi8uLi8uLi9zcmMvYmFja2VuZC9ob29rJ1xuXG5pbnN0YWxsSG9vayh3aW5kb3cpXG5cblxuXG4vKiogV0VCUEFDSyBGT09URVIgKipcbiAqKiAuL3NyYy9ob29rLmpzXG4gKiovIl0sInNvdXJjZVJvb3QiOiIifQ=="); 49 | 50 | /***/ }, 51 | 52 | /***/ 162: 53 | /***/ function(module, exports) { 54 | 55 | eval("'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.installHook = installHook;\n// this script is injected into every page.\n\n/**\n * Install the hook on window, which is an event emitter.\n * Note because Chrome content scripts cannot directly modify the window object,\n * we are evaling this function by inserting a script tag. That's why we have\n * to inline the whole event emitter implementation here.\n *\n * @param {Window} window\n */\n\nfunction installHook(window) {\n var listeners = {};\n\n var hook = {\n Vue: null,\n\n on: function on(event, fn) {\n event = '$' + event;(listeners[event] || (listeners[event] = [])).push(fn);\n },\n once: function once(event, fn) {\n event = '$' + event;\n function on() {\n this.off(event, on);\n fn.apply(this, arguments);\n }\n ;(listeners[event] || (listeners[event] = [])).push(on);\n },\n off: function off(event, fn) {\n event = '$' + event;\n if (!arguments.length) {\n listeners = {};\n } else {\n var cbs = listeners[event];\n if (cbs) {\n if (!fn) {\n listeners[event] = null;\n } else {\n for (var i = 0, l = cbs.length; i < l; i++) {\n var cb = cbs[i];\n if (cb === fn || cb.fn === fn) {\n cbs.splice(i, 1);\n break;\n }\n }\n }\n }\n }\n },\n emit: function emit(event) {\n event = '$' + event;\n var cbs = listeners[event];\n if (cbs) {\n var args = [].slice.call(arguments, 1);\n cbs = cbs.slice();\n for (var i = 0, l = cbs.length; i < l; i++) {\n cbs[i].apply(this, args);\n }\n }\n }\n };\n\n hook.once('init', function (Vue) {\n hook.Vue = Vue;\n });\n\n hook.once('vuex:init', function (store) {\n hook.store = store;\n });\n\n Object.defineProperty(window, '__VUE_DEVTOOLS_GLOBAL_HOOK__', {\n get: function get() {\n return hook;\n }\n });\n}\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vL1VzZXJzL2Jyc3Rld2FyL1JlcG9zaXRvcmllcy9lbGVjdHJvbi1ib2lsZXJwbGF0ZS12dWUvdG9vbHMvdnVlLWRldnRvb2xzL3NyYy9iYWNrZW5kL2hvb2suanM/MjdkNSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztRQVdnQjs7Ozs7Ozs7Ozs7O0FBQVQsU0FBUyxXQUFULENBQXNCLE1BQXRCLEVBQThCO0FBQ25DLE1BQUksWUFBWSxFQUFaLENBRCtCOztBQUduQyxNQUFNLE9BQU87QUFDWCxTQUFLLElBQUw7O0FBRUEsb0JBQUksT0FBTyxJQUFJO0FBQ2IsY0FBUSxNQUFNLEtBQU4sQ0FESyxDQUVYLFVBQVUsS0FBVixNQUFxQixVQUFVLEtBQVYsSUFBbUIsRUFBbkIsQ0FBckIsQ0FBRCxDQUE4QyxJQUE5QyxDQUFtRCxFQUFuRCxFQUZZO0tBSEo7QUFRWCx3QkFBTSxPQUFPLElBQUk7QUFDZixjQUFRLE1BQU0sS0FBTixDQURPO0FBRWYsZUFBUyxFQUFULEdBQWU7QUFDYixhQUFLLEdBQUwsQ0FBUyxLQUFULEVBQWdCLEVBQWhCLEVBRGE7QUFFYixXQUFHLEtBQUgsQ0FBUyxJQUFULEVBQWUsU0FBZixFQUZhO09BQWY7QUFJQSxPQU5lLENBTWIsVUFBVSxLQUFWLE1BQXFCLFVBQVUsS0FBVixJQUFtQixFQUFuQixDQUFyQixDQUFELENBQThDLElBQTlDLENBQW1ELEVBQW5ELEVBTmM7S0FSTjtBQWlCWCxzQkFBSyxPQUFPLElBQUk7QUFDZCxjQUFRLE1BQU0sS0FBTixDQURNO0FBRWQsVUFBSSxDQUFDLFVBQVUsTUFBVixFQUFrQjtBQUNyQixvQkFBWSxFQUFaLENBRHFCO09BQXZCLE1BRU87QUFDTCxZQUFNLE1BQU0sVUFBVSxLQUFWLENBQU4sQ0FERDtBQUVMLFlBQUksR0FBSixFQUFTO0FBQ1AsY0FBSSxDQUFDLEVBQUQsRUFBSztBQUNQLHNCQUFVLEtBQVYsSUFBbUIsSUFBbkIsQ0FETztXQUFULE1BRU87QUFDTCxpQkFBSyxJQUFJLElBQUksQ0FBSixFQUFPLElBQUksSUFBSSxNQUFKLEVBQVksSUFBSSxDQUFKLEVBQU8sR0FBdkMsRUFBNEM7QUFDMUMsa0JBQUksS0FBSyxJQUFJLENBQUosQ0FBTCxDQURzQztBQUUxQyxrQkFBSSxPQUFPLEVBQVAsSUFBYSxHQUFHLEVBQUgsS0FBVSxFQUFWLEVBQWM7QUFDN0Isb0JBQUksTUFBSixDQUFXLENBQVgsRUFBYyxDQUFkLEVBRDZCO0FBRTdCLHNCQUY2QjtlQUEvQjthQUZGO1dBSEY7U0FERjtPQUpGO0tBbkJTO0FBdUNYLHdCQUFNLE9BQU87QUFDWCxjQUFRLE1BQU0sS0FBTixDQURHO0FBRVgsVUFBSSxNQUFNLFVBQVUsS0FBVixDQUFOLENBRk87QUFHWCxVQUFJLEdBQUosRUFBUztBQUNQLFlBQU0sT0FBTyxHQUFHLEtBQUgsQ0FBUyxJQUFULENBQWMsU0FBZCxFQUF5QixDQUF6QixDQUFQLENBREM7QUFFUCxjQUFNLElBQUksS0FBSixFQUFOLENBRk87QUFHUCxhQUFLLElBQUksSUFBSSxDQUFKLEVBQU8sSUFBSSxJQUFJLE1BQUosRUFBWSxJQUFJLENBQUosRUFBTyxHQUF2QyxFQUE0QztBQUMxQyxjQUFJLENBQUosRUFBTyxLQUFQLENBQWEsSUFBYixFQUFtQixJQUFuQixFQUQwQztTQUE1QztPQUhGO0tBMUNTO0dBQVAsQ0FINkI7O0FBdURuQyxPQUFLLElBQUwsQ0FBVSxNQUFWLEVBQWtCLGVBQU87QUFDdkIsU0FBSyxHQUFMLEdBQVcsR0FBWCxDQUR1QjtHQUFQLENBQWxCLENBdkRtQzs7QUEyRG5DLE9BQUssSUFBTCxDQUFVLFdBQVYsRUFBdUIsaUJBQVM7QUFDOUIsU0FBSyxLQUFMLEdBQWEsS0FBYixDQUQ4QjtHQUFULENBQXZCLENBM0RtQzs7QUErRG5DLFNBQU8sY0FBUCxDQUFzQixNQUF0QixFQUE4Qiw4QkFBOUIsRUFBOEQ7QUFDNUQsd0JBQU87QUFDTCxhQUFPLElBQVAsQ0FESztLQURxRDtHQUE5RCxFQS9EbUMiLCJmaWxlIjoiMTYyLmpzIiwic291cmNlc0NvbnRlbnQiOlsiLy8gdGhpcyBzY3JpcHQgaXMgaW5qZWN0ZWQgaW50byBldmVyeSBwYWdlLlxuXG4vKipcbiAqIEluc3RhbGwgdGhlIGhvb2sgb24gd2luZG93LCB3aGljaCBpcyBhbiBldmVudCBlbWl0dGVyLlxuICogTm90ZSBiZWNhdXNlIENocm9tZSBjb250ZW50IHNjcmlwdHMgY2Fubm90IGRpcmVjdGx5IG1vZGlmeSB0aGUgd2luZG93IG9iamVjdCxcbiAqIHdlIGFyZSBldmFsaW5nIHRoaXMgZnVuY3Rpb24gYnkgaW5zZXJ0aW5nIGEgc2NyaXB0IHRhZy4gVGhhdCdzIHdoeSB3ZSBoYXZlXG4gKiB0byBpbmxpbmUgdGhlIHdob2xlIGV2ZW50IGVtaXR0ZXIgaW1wbGVtZW50YXRpb24gaGVyZS5cbiAqXG4gKiBAcGFyYW0ge1dpbmRvd30gd2luZG93XG4gKi9cblxuZXhwb3J0IGZ1bmN0aW9uIGluc3RhbGxIb29rICh3aW5kb3cpIHtcbiAgbGV0IGxpc3RlbmVycyA9IHt9XG5cbiAgY29uc3QgaG9vayA9IHtcbiAgICBWdWU6IG51bGwsXG5cbiAgICBvbiAoZXZlbnQsIGZuKSB7XG4gICAgICBldmVudCA9ICckJyArIGV2ZW50XG4gICAgICA7KGxpc3RlbmVyc1tldmVudF0gfHwgKGxpc3RlbmVyc1tldmVudF0gPSBbXSkpLnB1c2goZm4pXG4gICAgfSxcblxuICAgIG9uY2UgKGV2ZW50LCBmbikge1xuICAgICAgZXZlbnQgPSAnJCcgKyBldmVudFxuICAgICAgZnVuY3Rpb24gb24gKCkge1xuICAgICAgICB0aGlzLm9mZihldmVudCwgb24pXG4gICAgICAgIGZuLmFwcGx5KHRoaXMsIGFyZ3VtZW50cylcbiAgICAgIH1cbiAgICAgIDsobGlzdGVuZXJzW2V2ZW50XSB8fCAobGlzdGVuZXJzW2V2ZW50XSA9IFtdKSkucHVzaChvbilcbiAgICB9LFxuXG4gICAgb2ZmIChldmVudCwgZm4pIHtcbiAgICAgIGV2ZW50ID0gJyQnICsgZXZlbnRcbiAgICAgIGlmICghYXJndW1lbnRzLmxlbmd0aCkge1xuICAgICAgICBsaXN0ZW5lcnMgPSB7fVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgY29uc3QgY2JzID0gbGlzdGVuZXJzW2V2ZW50XVxuICAgICAgICBpZiAoY2JzKSB7XG4gICAgICAgICAgaWYgKCFmbikge1xuICAgICAgICAgICAgbGlzdGVuZXJzW2V2ZW50XSA9IG51bGxcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgZm9yIChsZXQgaSA9IDAsIGwgPSBjYnMubGVuZ3RoOyBpIDwgbDsgaSsrKSB7XG4gICAgICAgICAgICAgIGxldCBjYiA9IGNic1tpXVxuICAgICAgICAgICAgICBpZiAoY2IgPT09IGZuIHx8IGNiLmZuID09PSBmbikge1xuICAgICAgICAgICAgICAgIGNicy5zcGxpY2UoaSwgMSlcbiAgICAgICAgICAgICAgICBicmVha1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcblxuICAgIGVtaXQgKGV2ZW50KSB7XG4gICAgICBldmVudCA9ICckJyArIGV2ZW50XG4gICAgICBsZXQgY2JzID0gbGlzdGVuZXJzW2V2ZW50XVxuICAgICAgaWYgKGNicykge1xuICAgICAgICBjb25zdCBhcmdzID0gW10uc2xpY2UuY2FsbChhcmd1bWVudHMsIDEpXG4gICAgICAgIGNicyA9IGNicy5zbGljZSgpXG4gICAgICAgIGZvciAobGV0IGkgPSAwLCBsID0gY2JzLmxlbmd0aDsgaSA8IGw7IGkrKykge1xuICAgICAgICAgIGNic1tpXS5hcHBseSh0aGlzLCBhcmdzKVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgaG9vay5vbmNlKCdpbml0JywgVnVlID0+IHtcbiAgICBob29rLlZ1ZSA9IFZ1ZVxuICB9KVxuXG4gIGhvb2sub25jZSgndnVleDppbml0Jywgc3RvcmUgPT4ge1xuICAgIGhvb2suc3RvcmUgPSBzdG9yZVxuICB9KVxuXG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh3aW5kb3csICdfX1ZVRV9ERVZUT09MU19HTE9CQUxfSE9PS19fJywge1xuICAgIGdldCAoKSB7XG4gICAgICByZXR1cm4gaG9va1xuICAgIH1cbiAgfSlcbn1cblxuXG5cbi8qKiBXRUJQQUNLIEZPT1RFUiAqKlxuICoqIC9Vc2Vycy9icnN0ZXdhci9SZXBvc2l0b3JpZXMvZWxlY3Ryb24tYm9pbGVycGxhdGUtdnVlL3Rvb2xzL3Z1ZS1kZXZ0b29scy9zcmMvYmFja2VuZC9ob29rLmpzXG4gKiovIl0sInNvdXJjZVJvb3QiOiIifQ=="); 56 | 57 | /***/ } 58 | 59 | /******/ }); -------------------------------------------------------------------------------- /tools/vue-devtools/index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | Vue Devtools 13 | 37 | 38 | 39 | 40 | 41 |
42 | 43 | 44 | 45 | --------------------------------------------------------------------------------