├── config
└── app-config.json
├── .browserslistrc
├── babel.config.js
├── postcss.config.js
├── src.preload
├── preload.js
├── preload-for-user-contents.js
└── preload-isolated.js
├── index.js
├── src
├── shims-vue.d.ts
├── assets
│ └── logo.png
├── views
│ ├── About.vue
│ └── Home.vue
├── windows
│ ├── MainWindow
│ │ ├── main.ts
│ │ ├── App.vue
│ │ └── router.ts
│ └── SubWindow
│ │ ├── main.ts
│ │ ├── App.vue
│ │ └── router.ts
├── shims-tsx.d.ts
└── components
│ └── HelloWorld.vue
├── public
├── favicon.ico
├── sub-window.html
└── main-window.html
├── electron-builder.yml
├── src.mainproc
├── ipc
│ ├── app.ts
│ └── views.Home.ts
├── lib
│ ├── conf.ts
│ └── SimpleWindowManager.ts
├── main.ts
└── windows
│ ├── UserContentWindow.ts
│ ├── SubWindow.ts
│ └── MainWindow.ts
├── tslint.json
├── tests
└── unit
│ └── example.spec.ts
├── .vscode
└── launch.json
├── vue.config.js
├── .gitignore
├── .npmignore
├── tsconfig.json
├── tsconfig.mainproc.json
├── webpack.mainproc.config.js
├── README.md
└── package.json
/config/app-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "foo": "bar"
3 | }
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not ie <= 8
4 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src.preload/preload.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | window.ipcRenderer = require('electron').ipcRenderer;
4 |
5 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // This file is electron main process bootstrap.
2 | //
3 |
4 | require('./bin/main');
5 |
6 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import Vue from 'vue';
3 | export default Vue;
4 | }
5 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shellyln/vue-electron-typescript-quickstart/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shellyln/vue-electron-typescript-quickstart/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src.preload/preload-for-user-contents.js:
--------------------------------------------------------------------------------
1 |
2 | // nothing to do.
3 |
4 | // console.log("preload-for-user-contents:" + __filename);
5 |
--------------------------------------------------------------------------------
/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is an about page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/electron-builder.yml:
--------------------------------------------------------------------------------
1 | directories:
2 | output: dist.electron
3 | buildResources: build
4 | appId: your.id
5 | mac:
6 | category: your.app.category.type
7 | asarUnpack: config/**
8 |
--------------------------------------------------------------------------------
/src.mainproc/ipc/app.ts:
--------------------------------------------------------------------------------
1 |
2 | import { ipcMain } from 'electron';
3 |
4 |
5 |
6 | // ipcMain.on('app:awesome-sub-module:message-foo', (event: any, arg: any) => {
7 | // //
8 | // });
9 |
--------------------------------------------------------------------------------
/src/windows/MainWindow/main.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App.vue';
3 | import router from './router';
4 |
5 | Vue.config.productionTip = false;
6 |
7 | new Vue({
8 | router,
9 | render: (h) => h(App),
10 | }).$mount('#app');
11 |
--------------------------------------------------------------------------------
/src/windows/SubWindow/main.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App.vue';
3 | import router from './router';
4 |
5 | Vue.config.productionTip = false;
6 |
7 | new Vue({
8 | router,
9 | render: (h) => h(App),
10 | }).$mount('#app');
11 |
--------------------------------------------------------------------------------
/src.mainproc/lib/conf.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as fs from 'fs';
3 | import * as path from 'path';
4 | import { app } from 'electron';
5 |
6 |
7 |
8 | export const appConfig = JSON.parse(
9 | fs.readFileSync(path.join(app.getAppPath(), 'config/app-config.json')).toString());
10 |
--------------------------------------------------------------------------------
/src.preload/preload-isolated.js:
--------------------------------------------------------------------------------
1 |
2 | {
3 | const { contextBridge, ipcRenderer } = require('electron');
4 |
5 | contextBridge.exposeInMainWorld(
6 | 'myAppApi', {
7 | ping: async (value) => {
8 | return ipcRenderer.invoke('views:Home:message-foo', value);
9 | },
10 | }
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/src/shims-tsx.d.ts:
--------------------------------------------------------------------------------
1 | import Vue, { VNode } from 'vue';
2 |
3 | declare global {
4 | namespace JSX {
5 | // tslint:disable no-empty-interface
6 | interface Element extends VNode { }
7 | // tslint:disable no-empty-interface
8 | interface ElementClass extends Vue { }
9 | interface IntrinsicElements {
10 | [elem: string]: any;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "warning",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 | "linterOptions": {
7 | "exclude": [
8 | "node_modules/**"
9 | ]
10 | },
11 | "rules": {
12 | "quotemark": [true, "single"],
13 | "indent": [true, "spaces", 4],
14 | "interface-name": false,
15 | "ordered-imports": false,
16 | "object-literal-sort-keys": false,
17 | "no-consecutive-blank-lines": false
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/unit/example.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { shallowMount } from '@vue/test-utils';
3 | import HelloWorld from '@/components/HelloWorld.vue';
4 |
5 | describe('HelloWorld.vue', () => {
6 | it('renders props.msg when passed', () => {
7 | const msg = 'new message';
8 | const wrapper = shallowMount(HelloWorld, {
9 | propsData: { msg },
10 | });
11 | expect(wrapper.text()).to.include(msg);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/public/sub-window.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | sub window
9 |
10 |
11 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/public/main-window.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-hello-world
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src.mainproc/lib/SimpleWindowManager.ts:
--------------------------------------------------------------------------------
1 |
2 | import { BrowserWindow } from 'electron';
3 |
4 |
5 |
6 | const windowMap = new Map();
7 |
8 |
9 | export function registerWindow(key: symbol | BrowserWindow, wnd: BrowserWindow) {
10 | windowMap.set(key, wnd);
11 | }
12 |
13 |
14 | export function unregisterWindow(key: symbol | BrowserWindow) {
15 | return windowMap.delete(key);
16 | }
17 |
18 |
19 | export function isRegistered(key: symbol | BrowserWindow) {
20 | return windowMap.has(key);
21 | }
22 |
23 |
24 | export function getWindow(key: symbol | BrowserWindow) {
25 | return windowMap.get(key);
26 | }
27 |
--------------------------------------------------------------------------------
/src.mainproc/ipc/views.Home.ts:
--------------------------------------------------------------------------------
1 |
2 | import { ipcMain } from 'electron';
3 | import { createSubWindow } from '../windows/SubWindow';
4 | import { createUserContentWindow } from '../windows/UserContentWindow';
5 |
6 |
7 |
8 | ipcMain.handle('views:Home:message-foo', (event: any, message: string) => {
9 | // tslint:disable-next-line:no-console
10 | console.log(message);
11 |
12 | switch (message) {
13 | case 'ping':
14 | createSubWindow();
15 | // TODO: BUG: MainWindow and SubWindow's devtools can't open by CSP if UserContentWindow is created.
16 | createUserContentWindow();
17 | break;
18 | }
19 | return 'pong';
20 | });
21 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Debug Main Process",
9 | "type": "node",
10 | "request": "launch",
11 | "cwd": "${workspaceRoot}",
12 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
13 | "windows": {
14 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
15 | },
16 | "args" : ["."]
17 | }
18 | ]
19 | }
--------------------------------------------------------------------------------
/src/windows/MainWindow/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Home|
5 | About
6 |
7 |
Main Window
8 |
9 |
10 |
11 |
12 |
13 |
14 |
33 |
--------------------------------------------------------------------------------
/src/windows/SubWindow/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Home|
5 | About
6 |
7 |
Sub Window
8 |
9 |
10 |
11 |
12 |
13 |
14 |
33 |
--------------------------------------------------------------------------------
/src/windows/MainWindow/router.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 | import Home from '../../views/Home.vue';
4 |
5 | Vue.use(Router);
6 |
7 | const router = new Router({
8 | mode: 'history',
9 | base: process.env.BASE_URL,
10 | routes: [
11 | {
12 | path: '/',
13 | name: 'home',
14 | component: Home,
15 | },
16 | {
17 | path: '*',
18 | redirect: '/',
19 | },
20 | {
21 | path: '/about',
22 | name: 'about',
23 | // route level code-splitting
24 | // this generates a separate chunk (about.[hash].js) for this route
25 | // which is lazy-loaded when the route is visited.
26 | component: () => import(/* webpackChunkName: "about" */ '../../views/About.vue'),
27 | },
28 | ],
29 | });
30 |
31 | export default router;
32 |
--------------------------------------------------------------------------------
/src/windows/SubWindow/router.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 | import Home from '../../views/Home.vue';
4 |
5 | Vue.use(Router);
6 |
7 | const router = new Router({
8 | mode: 'history',
9 | base: process.env.BASE_URL,
10 | routes: [
11 | {
12 | path: '/',
13 | name: 'home',
14 | component: Home,
15 | },
16 | {
17 | path: '*',
18 | redirect: '/about',
19 | },
20 | {
21 | path: '/about',
22 | name: 'about',
23 | // route level code-splitting
24 | // this generates a separate chunk (about.[hash].js) for this route
25 | // which is lazy-loaded when the route is visited.
26 | component: () => import(/* webpackChunkName: "about" */ '../../views/About.vue'),
27 | },
28 | ],
29 | });
30 |
31 | export default router;
32 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
38 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | pages: {
3 | mainwindow: {
4 | // entry for the page
5 | entry: 'src/windows/MainWindow/main.ts',
6 | // the source template
7 | template: 'public/main-window.html',
8 | // output as dist/index.html
9 | filename: 'main-window.html',
10 | // when using title option,
11 | // template title tag needs to be <%= htmlWebpackPlugin.options.title %>
12 | //title: 'Index Page',
13 | // chunks to include on this page, by default includes
14 | // extracted common chunks and vendor chunks.
15 | //chunks: ['chunk-vendors', 'chunk-common', 'index']
16 | },
17 | // when using the entry-only string format,
18 | // template is inferred to be `public/subpage.html`
19 | // and falls back to `public/index.html` if not found.
20 | // Output filename is inferred to be `subpage.html`.
21 | subwindow: {
22 | entry: 'src/windows/SubWindow/main.ts',
23 | template: 'public/sub-window.html',
24 | filename: 'sub-window.html',
25 | },
26 | }
27 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # local env files
4 | .env.local
5 | .env.*.local
6 |
7 | # Log files
8 | logs
9 | *.log
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # node-waf configuration
29 | .lock-wscript
30 |
31 | # Optional npm cache directory
32 | .npm
33 |
34 | # Optional REPL history
35 | .node_repl_history
36 |
37 | # Compiled binary addons (http://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Visual Studio files
41 | /.vs/
42 |
43 | # VS Code cache files
44 | /.vscode/.browse.VC*
45 |
46 | # Dependency directory
47 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
48 | /node_modules/
49 |
50 | /bin/
51 | /dist/
52 | /dist.electron/
53 |
54 | # Editor directories and files
55 | .idea
56 | # .vscode
57 | *.suo
58 | *.ntvs*
59 | *.njsproj
60 | *.sln
61 | *.sw*
62 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # local env files
4 | .env.local
5 | .env.*.local
6 |
7 | # Log files
8 | logs
9 | *.log
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # node-waf configuration
29 | .lock-wscript
30 |
31 | # Optional npm cache directory
32 | .npm
33 |
34 | # Optional REPL history
35 | .node_repl_history
36 |
37 | # Compiled binary addons (http://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Visual Studio files
41 | /.vs/
42 |
43 | # VS Code cache files
44 | /.vscode/.browse.VC*
45 |
46 | # Dependency directory
47 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
48 | /node_modules/
49 |
50 | /bin/
51 | /dist/
52 | /dist.electron/
53 |
54 | # Editor directories and files
55 | .idea
56 | # .vscode
57 | *.suo
58 | *.ntvs*
59 | *.njsproj
60 | *.sln
61 | *.sw*
62 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "experimentalDecorators": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "sourceMap": true,
13 |
14 | "noEmitOnError": true,
15 | "noImplicitAny": true,
16 | "suppressImplicitAnyIndexErrors": true,
17 | "noImplicitReturns": true,
18 | "noImplicitThis": true,
19 | "strictNullChecks": true,
20 | // "noUnusedParameters": true,
21 | // "noUnusedLocals": true,
22 | // "noFallthroughCasesInSwitch": true,
23 |
24 | "baseUrl": ".",
25 | "types": [
26 | "webpack-env",
27 | "mocha",
28 | "chai"
29 | ],
30 | "paths": {
31 | "@/*": [
32 | "src/*"
33 | ]
34 | },
35 | "lib": [
36 | "esnext",
37 | "dom",
38 | "dom.iterable",
39 | "scripthost"
40 | ]
41 | },
42 | "include": [
43 | "src/**/*.ts",
44 | "src/**/*.tsx",
45 | "src/**/*.vue",
46 | "tests/**/*.ts",
47 | "tests/**/*.tsx"
48 | ],
49 | "exclude": [
50 | "node_modules"
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/tsconfig.mainproc.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "react",
7 | "jsxFactory": "FooBar.dom",
8 | "importHelpers": true,
9 | "moduleResolution": "node",
10 | "experimentalDecorators": true,
11 | "emitDecoratorMetadata": true,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "sourceMap": true,
15 |
16 | "noEmitOnError": true,
17 | "noImplicitAny": true,
18 | "suppressImplicitAnyIndexErrors": true,
19 | "noImplicitReturns": true,
20 | "noImplicitThis": true,
21 | "strictNullChecks": true,
22 | // "noUnusedParameters": true,
23 | // "noUnusedLocals": true,
24 | // "noFallthroughCasesInSwitch": true,
25 |
26 | "baseUrl": ".",
27 | "types": ["node", "electron"],
28 | "paths": {
29 | },
30 | "lib": [
31 | "esnext",
32 | "scripthost"
33 | ],
34 | // "declaration": true,
35 | // "declarationDir": "./declarations",
36 | // "traceResolution": true,
37 | },
38 | "include": [
39 | "src.mainproc/**/*"
40 | ],
41 | "exclude": [
42 | "bin",
43 | "node_modules",
44 | "spec",
45 | "src",
46 | "public",
47 | "tests"
48 | ]
49 | }
--------------------------------------------------------------------------------
/webpack.mainproc.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 |
5 |
6 | module.exports = function (env) {
7 | return [{
8 | target: "electron-main",
9 | entry: {
10 | spec: [
11 | path.resolve(__dirname, 'src.mainproc/main.ts')
12 | ]
13 | },
14 | output: {
15 | library: 'main',
16 |
17 | libraryTarget: 'commonjs2',
18 | filename: 'main.js',
19 | path: path.resolve(__dirname, 'bin'),
20 | devtoolModuleFilenameTemplate: '../[resource-path]',
21 | // devtoolModuleFilenameTemplate: void 0
22 | },
23 | node: {
24 | __filename: false,
25 | __dirname: false,
26 | },
27 | module: {
28 | rules: [{
29 | test: /\.tsx?$/,
30 | use: [
31 | 'babel-loader',
32 | 'ts-loader?' + JSON.stringify({
33 | configFile: 'tsconfig.mainproc.json'
34 | }),
35 | ],
36 | exclude: /node_modules[\/\\].*$/
37 | }, {
38 | test: /\.jsx?$/,
39 | use: ['babel-loader'],
40 | exclude: /node_modules[\/\\].*$/
41 | }, {
42 | enforce: 'pre',
43 | test: /\.[tj]sx?$/,
44 | use: {
45 | loader: 'source-map-loader',
46 | options: {
47 | }
48 | },
49 | exclude: /node_modules[\/\\].*$/
50 | }]
51 | },
52 | plugins: [],
53 | resolve: {
54 | extensions: ['.tsx', '.ts', '.jsx', '.js']
55 | },
56 | devtool: 'source-map'
57 | },
58 |
59 | ]}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-electron-typescript-quickstart
2 |
3 | A boilerplate of Electron app that uses Vue in TypeScript.
4 | This is based on the output of Vue CLI 3 and is not ejecting from the CLI.
5 |
6 |
7 | ## Usage
8 | ```
9 | git clone https://github.com/shellyln/vue-electron-typescript-quickstart.git
10 | cd vue-electron-typescript-quickstart
11 |
12 | vi package.json
13 | # and edit package name, author, ...
14 |
15 | rm package-lock.json
16 | npm install
17 |
18 | rm -rf .git/
19 | git init
20 | git add .
21 | git commit -m "initial commit."
22 |
23 | npm run build
24 | npm run build:mainproc
25 | npm start
26 | ```
27 |
28 | ---
29 |
30 | ## Project setup
31 | ```
32 | npm install
33 | ```
34 |
35 | ### ~~Compiles and hot-reloads for development~~
36 | ```
37 | npm run serve
38 | ```
39 |
40 | ### Compiles and minifies for production (electron renderer process)
41 | ```
42 | npm run build
43 | ```
44 |
45 | ### Compiles and minifies for production (electron main process)
46 | ```
47 | npm run build:mainproc
48 | ```
49 |
50 | ### Clean project
51 | ```
52 | npm run clean
53 | ```
54 |
55 | ### Start electron app for debug
56 | ```
57 | npm run start
58 | ```
59 |
60 | ### Build electron distribution executable files (unpacked)
61 | ```
62 | npm run pack
63 | ```
64 |
65 | ### Build electron distribution executable files (packing to the installer)
66 | ```
67 | npm run dist
68 | ```
69 |
70 | ### Run your tests
71 | ```
72 | npm run test
73 | ```
74 |
75 | ### Lints and fixes files
76 | ```
77 | npm run lint
78 | ```
79 |
80 | ### Run your unit tests (renderer process)
81 | ```
82 | npm run test:unit
83 | ```
84 |
85 | ### Customize configuration
86 | See [Configuration Reference](https://cli.vuejs.org/config/).
87 |
88 |
89 | ---
90 |
91 |
92 | ## **Electron Documentation (security)**
93 | See [this](https://electronjs.org/docs/tutorial/security) guide.
94 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-electron-typescript-quickstart",
3 | "version": "0.1.4",
4 | "private": false,
5 | "description": "A boilerplate of Electron app that uses Vue in TypeScript.",
6 | "keywords": [
7 | "Vue.js",
8 | "Electron",
9 | "TypeScript",
10 | "JavaScript",
11 | "boilerplate",
12 | "quickstart",
13 | "template"
14 | ],
15 | "scripts": {
16 | "start": "electron .",
17 | "pack": "electron-builder --dir -c electron-builder.yml",
18 | "dist": "electron-builder -c electron-builder.yml",
19 | "serve": "vue-cli-service serve",
20 | "build": "vue-cli-service build",
21 | "build:dev": "vue-cli-service build --mode development",
22 | "build:mainproc": "webpack-cli --mode=development --config webpack.mainproc.config.js",
23 | "clean": "rm -rf ./dist && rm -rf ./bin && rm -rf ./dist.electron",
24 | "lint": "vue-cli-service lint",
25 | "test": "npm run test:unit",
26 | "test:unit": "vue-cli-service test:unit"
27 | },
28 | "dependencies": {
29 | "vue": "^2.6.12",
30 | "vue-class-component": "^7.2.6",
31 | "vue-property-decorator": "^9.1.2",
32 | "vue-router": "^3.4.9"
33 | },
34 | "devDependencies": {
35 | "@types/chai": "^4.2.14",
36 | "@types/electron": "^1.6.10",
37 | "@types/mocha": "^8.2.0",
38 | "@vue/cli-plugin-babel": "^4.5.9",
39 | "@vue/cli-plugin-typescript": "^4.5.9",
40 | "@vue/cli-plugin-unit-mocha": "^4.5.9",
41 | "@vue/cli-service": "^4.5.9",
42 | "@vue/test-utils": "^1.1.2",
43 | "chai": "^4.2.0",
44 | "electron": "^11.1.1",
45 | "electron-builder": "^22.9.1",
46 | "node-sass": "^5.0.0",
47 | "sass-loader": "^10.1.0",
48 | "source-map-loader": "^1.1.3",
49 | "typescript": "^4.1.3",
50 | "vue-template-compiler": "^2.6.12",
51 | "webpack": "4.44.2",
52 | "webpack-cli": "^4.3.0"
53 | },
54 | "repository": {
55 | "type": "git",
56 | "url": "https://github.com/shellyln/vue-electron-typescript-quickstart.git"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src.mainproc/main.ts:
--------------------------------------------------------------------------------
1 |
2 | // Modules to control application life and create native browser window
3 | import * as url from 'url';
4 | import * as path from 'path';
5 | import { app, protocol, Menu } from 'electron';
6 |
7 |
8 | // Window
9 |
10 | import { appConfig } from './lib/conf';
11 | import { createMainWindow } from './windows/MainWindow';
12 |
13 |
14 | // IPC events
15 |
16 | import './ipc/app';
17 | import './ipc/views.Home';
18 |
19 |
20 | // Read the application config.
21 | // tslint:disable-next-line:no-console
22 | console.log('app config: ' + JSON.stringify(appConfig, null, 2));
23 |
24 |
25 | if (app.isPackaged) {
26 | // Removing the menu bar from the window.
27 | Menu.setApplicationMenu(null);
28 | }
29 |
30 |
31 | // App lifecycle events.
32 |
33 | // This method will be called when Electron has finished
34 | // initialization and is ready to create browser windows.
35 | // Some APIs can only be used after this event occurs.
36 | // tslint:disable-next-line:only-arrow-functions
37 | app.on('ready', function() {
38 | protocol.interceptFileProtocol('file', (req, callback) => {
39 | let filePath = new url.URL(req.url).pathname;
40 | if (process.platform === 'win32') {
41 | if (filePath.match(/^\/[A-Za-z]:/)) {
42 | filePath = filePath.slice(1);
43 | }
44 | if (filePath.match(/^[A-Za-z]:\/(css|img|js)/)) {
45 | filePath = path.join(app.getAppPath(), 'dist', filePath.slice(3));
46 | } else if (filePath.match(/^[A-Za-z]:\/[^/\\]+?\.(js|css|png|jpeg|jpg|ico|svg)$/)) {
47 | // case of "vue-cli-service build --mode development"
48 | filePath = path.join(app.getAppPath(), 'dist', filePath.slice(3));
49 | }
50 | } else {
51 | if (filePath.match(/^\/(css|img|js)/)) {
52 | filePath = path.join(app.getAppPath(), 'dist', filePath.slice(1));
53 | } else if (filePath.match(/^\/[^/\\]+?\.(js|css|png|jpeg|jpg|ico|svg)$/)) {
54 | // case of "vue-cli-service build --mode development"
55 | filePath = path.join(app.getAppPath(), 'dist', filePath.slice(1));
56 | }
57 | }
58 | callback(path.normalize(filePath));
59 | });
60 | createMainWindow();
61 | });
62 |
63 |
64 | // Quit when all windows are closed.
65 | // tslint:disable-next-line:only-arrow-functions
66 | app.on('window-all-closed', function() {
67 | // On macOS it is common for applications and their menu bar
68 | // to stay active until the user quits explicitly with Cmd + Q
69 | if (process.platform !== 'darwin') {
70 | app.quit();
71 | }
72 | });
73 |
74 |
75 | // tslint:disable-next-line:only-arrow-functions
76 | app.on('activate', function() {
77 | // On macOS it's common to re-create a window in the app when the
78 | // dock icon is clicked and there are no other windows open.
79 | createMainWindow();
80 | });
81 |
82 |
83 | // In this file you can include the rest of your app's specific main process
84 | // code. You can also put them in separate files and require them here.
85 |
--------------------------------------------------------------------------------
/src.mainproc/windows/UserContentWindow.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as path from 'path';
3 | import { app, BrowserWindow, shell } from 'electron';
4 | import { registerWindow, unregisterWindow, isRegistered, getWindow } from '../lib/SimpleWindowManager';
5 |
6 |
7 |
8 | const userContentWindowSymbol = Symbol();
9 |
10 |
11 | // Keep a global reference of the window object, if you don't, the window will
12 | // be closed automatically when the JavaScript object is garbage collected.
13 |
14 | export function createUserContentWindow() {
15 | // UserContentWindow is singleton.
16 | if (isRegistered(userContentWindowSymbol)) {
17 | return getWindow(userContentWindowSymbol);
18 | }
19 |
20 | // Create the browser window.
21 | let userContentWindow: BrowserWindow | null = new BrowserWindow({
22 | webPreferences: {
23 | nodeIntegration: false, // Electron 11: default is false
24 | nodeIntegrationInWorker: false, // Electron 11: default is false
25 | nodeIntegrationInSubFrames: false, // Electron 11: default is false
26 | enableRemoteModule: false, // Electron 11: default is false
27 | contextIsolation: true,
28 | preload: path.join(app.getAppPath(), 'src.preload/preload-for-user-contents.js'),
29 | },
30 | width: 800,
31 | height: 600,
32 | });
33 | registerWindow(userContentWindowSymbol, userContentWindow);
34 |
35 | // and load the html of the app.
36 | userContentWindow.loadURL('https://shellyln.github.io/');
37 |
38 | userContentWindow.webContents.session.webRequest.onHeadersReceived((details: any, callback: any) => {
39 | const headers = {...details.responseHeaders};
40 | for (const key of Object.keys(headers)) {
41 | if (key.toLowerCase() === 'content-security-policy') {
42 | delete headers[key];
43 | }
44 | if (key.toLowerCase() === 'x-content-security-policy') {
45 | delete headers[key];
46 | }
47 | }
48 | callback({
49 | responseHeaders: {
50 | ...headers,
51 | 'content-security-policy': [
52 | `default-src 'self';` +
53 | `script-src chrome: 'self'${app.isPackaged ? '' : ` devtools: 'unsafe-eval'`};` +
54 | `style-src chrome: 'self' 'unsafe-inline'${app.isPackaged ? '' : ` devtools:`};` +
55 | `img-src 'self';` +
56 | `frame-ancestors 'none';`,
57 | ],
58 | },
59 | });
60 | });
61 |
62 | userContentWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
63 | // const url = webContents.getURL();
64 | //
65 | // if (permission === 'notifications') {
66 | // // Approves the permissions request
67 | // callback(true);
68 | // }
69 | // if (!url.startsWith('https://my-website.com')) {
70 | // // Denies the permissions request
71 | // return callback(false);
72 | // }
73 | return callback(false);
74 | });
75 |
76 | // Open the DevTools.
77 | // userContentWindow.webContents.openDevTools()
78 |
79 | // Emitted when the window is closed.
80 | userContentWindow.on('closed', () => {
81 | // Dereference the window object, usually you would store windows
82 | // in an array if your app supports multi windows, this is the time
83 | // when you should delete the corresponding element.
84 | unregisterWindow(userContentWindowSymbol);
85 | userContentWindow = null;
86 | });
87 |
88 | userContentWindow.webContents.on('new-window', (event: any, url: string) => {
89 | event.preventDefault();
90 | if (url.match(/^https?:\/\//)) {
91 | shell.openExternal(url);
92 | }
93 | });
94 |
95 | return userContentWindow;
96 | }
97 |
--------------------------------------------------------------------------------
/src.mainproc/windows/SubWindow.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as path from 'path';
3 | import { app, BrowserWindow, shell } from 'electron';
4 | import { registerWindow, unregisterWindow } from '../lib/SimpleWindowManager';
5 |
6 |
7 |
8 | // Keep a global reference of the window object, if you don't, the window will
9 | // be closed automatically when the JavaScript object is garbage collected.
10 |
11 | export function createSubWindow() {
12 | // Create the browser window.
13 | let subWindow: BrowserWindow | null = new BrowserWindow({
14 | webPreferences: {
15 | nodeIntegration: false, // Electron 11: default is false
16 | nodeIntegrationInWorker: false, // Electron 11: default is false
17 | nodeIntegrationInSubFrames: false, // Electron 11: default is false
18 | enableRemoteModule: false, // Electron 11: default is false
19 | contextIsolation: true,
20 | preload: path.join(app.getAppPath(), 'src.preload/preload-isolated.js'),
21 | },
22 | width: 800,
23 | height: 600,
24 | });
25 | registerWindow(subWindow, subWindow);
26 |
27 | // and load the html of the app.
28 | subWindow.loadFile(path.join(app.getAppPath(), 'dist/sub-window.html'));
29 |
30 | // CSP is not work while the location scheme is 'file'.
31 | // And when if navigated to http/https, CSP is to be enabled.
32 | if (app.isPackaged) {
33 | subWindow.webContents.session.webRequest.onHeadersReceived((details: any, callback: any) => {
34 | const headers = {...details.responseHeaders};
35 | for (const key of Object.keys(headers)) {
36 | if (key.toLowerCase() === 'content-security-policy') {
37 | delete headers[key];
38 | }
39 | if (key.toLowerCase() === 'x-content-security-policy') {
40 | delete headers[key];
41 | }
42 | }
43 | callback({
44 | responseHeaders: {
45 | ...headers,
46 | 'content-security-policy': [
47 | `default-src 'none';` +
48 | `frame-ancestors 'none';`,
49 | ],
50 | },
51 | });
52 | });
53 | } else {
54 | // NOTE: Remove CSP to use devtools.
55 | // Refused to load the script 'devtools://devtools/bundled/shell.js'
56 | // because it violates the following Content Security Policy directive: "default-src 'none'.
57 | subWindow.webContents.session.webRequest.onHeadersReceived((details: any, callback: any) => {
58 | callback({
59 | responseHeaders: {
60 | ...details.responseHeaders,
61 | },
62 | });
63 | });
64 | }
65 |
66 | subWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
67 | // const url = webContents.getURL();
68 | //
69 | // if (permission === 'notifications') {
70 | // // Approves the permissions request
71 | // callback(true);
72 | // }
73 | // if (!url.startsWith('https://my-website.com')) {
74 | // // Denies the permissions request
75 | // return callback(false);
76 | // }
77 | return callback(false);
78 | });
79 |
80 | // Open the DevTools.
81 | // subWindow.webContents.openDevTools()
82 |
83 | // Emitted when the window is closed.
84 | subWindow.on('closed', () => {
85 | // Dereference the window object, usually you would store windows
86 | // in an array if your app supports multi windows, this is the time
87 | // when you should delete the corresponding element.
88 | unregisterWindow(subWindow as BrowserWindow);
89 | subWindow = null;
90 | });
91 |
92 | subWindow.webContents.on('new-window', (event: any, url: string) => {
93 | event.preventDefault();
94 | if (url.match(/^https?:\/\//)) {
95 | shell.openExternal(url);
96 | }
97 | });
98 |
99 | return subWindow;
100 | }
101 |
--------------------------------------------------------------------------------
/src.mainproc/windows/MainWindow.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as path from 'path';
3 | import { app, BrowserWindow, shell } from 'electron';
4 | import { registerWindow, unregisterWindow, isRegistered, getWindow } from '../lib/SimpleWindowManager';
5 |
6 |
7 |
8 | const mainWindowSymbol = Symbol();
9 |
10 |
11 | // Keep a global reference of the window object, if you don't, the window will
12 | // be closed automatically when the JavaScript object is garbage collected.
13 |
14 | export function createMainWindow() {
15 | // MainWindow is singleton.
16 | if (isRegistered(mainWindowSymbol)) {
17 | return getWindow(mainWindowSymbol);
18 | }
19 |
20 | // Create the browser window.
21 | let mainWindow: BrowserWindow | null = new BrowserWindow({
22 | webPreferences: {
23 | nodeIntegration: false, // Electron 11: default is false
24 | nodeIntegrationInWorker: false, // Electron 11: default is false
25 | nodeIntegrationInSubFrames: false, // Electron 11: default is false
26 | enableRemoteModule: false, // Electron 11: default is false
27 | contextIsolation: true,
28 | preload: path.join(app.getAppPath(), 'src.preload/preload-isolated.js'),
29 | },
30 | width: 800,
31 | height: 600,
32 | });
33 | registerWindow(mainWindowSymbol, mainWindow);
34 |
35 | // and load the html of the app.
36 | mainWindow.loadFile(path.join(app.getAppPath(), 'dist/main-window.html'));
37 |
38 | // CSP is not work while the location scheme is 'file'.
39 | // And when if navigated to http/https, CSP is to be enabled.
40 | if (app.isPackaged) {
41 | mainWindow.webContents.session.webRequest.onHeadersReceived((details: any, callback: any) => {
42 | const headers = {...details.responseHeaders};
43 | for (const key of Object.keys(headers)) {
44 | if (key.toLowerCase() === 'content-security-policy') {
45 | delete headers[key];
46 | }
47 | if (key.toLowerCase() === 'x-content-security-policy') {
48 | delete headers[key];
49 | }
50 | }
51 | callback({
52 | responseHeaders: {
53 | ...headers,
54 | 'content-security-policy': [
55 | `default-src 'none';` +
56 | `frame-ancestors 'none';`,
57 | ],
58 | },
59 | });
60 | });
61 | } else {
62 | // NOTE: Remove CSP to use devtools.
63 | // Refused to load the script 'devtools://devtools/bundled/shell.js'
64 | // because it violates the following Content Security Policy directive: "default-src 'none'.
65 | mainWindow.webContents.session.webRequest.onHeadersReceived((details: any, callback: any) => {
66 | callback({
67 | responseHeaders: {
68 | ...details.responseHeaders,
69 | },
70 | });
71 | });
72 | }
73 |
74 | mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
75 | // const url = webContents.getURL();
76 | //
77 | // if (permission === 'notifications') {
78 | // // Approves the permissions request
79 | // callback(true);
80 | // }
81 | // if (!url.startsWith('https://my-website.com')) {
82 | // // Denies the permissions request
83 | // return callback(false);
84 | // }
85 | return callback(false);
86 | });
87 |
88 | // Open the DevTools.
89 | // mainWindow.webContents.openDevTools()
90 |
91 | // Emitted when the window is closed.
92 | mainWindow.on('closed', () => {
93 | // Dereference the window object, usually you would store windows
94 | // in an array if your app supports multi windows, this is the time
95 | // when you should delete the corresponding element.
96 | unregisterWindow(mainWindowSymbol);
97 | mainWindow = null;
98 | });
99 |
100 | mainWindow.webContents.on('new-window', (event: any, url: string) => {
101 | event.preventDefault();
102 | if (url.match(/^https?:\/\//)) {
103 | shell.openExternal(url);
104 | }
105 | });
106 |
107 | return mainWindow;
108 | }
109 |
--------------------------------------------------------------------------------
/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 |
check out the
7 | vue-cli documentation.
12 |
13 |
Installed CLI Plugins
14 |
37 |
Essential Links
38 |
55 |
Ecosystem
56 |
77 |
113 |
114 |
115 |
116 |
117 |
118 |
126 |
127 |
128 |
129 |
130 |
146 |
--------------------------------------------------------------------------------