├── .gitignore ├── LICENSE ├── README.md ├── bin └── create-vite-electron.js ├── package.json ├── project-script ├── base.ts ├── build.ts ├── dev.ts ├── index.ts └── tsconfig.json ├── scripts └── build.js ├── src ├── index.ts └── lib │ ├── project.ts │ └── utils.ts ├── template-js ├── index.html ├── scripts │ └── project.js └── src │ └── main │ ├── entry.js │ └── protocol.js ├── template-ts ├── index.html ├── scripts │ └── project.js └── src │ └── main │ ├── entry.ts │ └── protocol.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | npm package 3 |

4 | 5 | # CVEP 6 | 7 | ### Create the Electron project based on the next generation development server vite and vue3. 8 | 9 | 10 | > Project use vite2.0,vue3, and the latest electron.Additional items to be added such as ElementPlus, Vuex, etc. 11 | > 12 | > **Compatibility Note:** Vite requires [Node.js](https://nodejs.org/en/) version >=12.0.0. 13 | 14 | 15 | - ⏱ Quickly create a project 16 | - ⚡️ Lightning Fast HMR 17 | - 🛠️ Dev&&Build 18 | - 📦 No configuration 19 | 20 | 21 | ## >Use 22 | 23 | With NPM: 24 | 25 | ``` 26 | $ npm install cvep -g 27 | ``` 28 | 29 | With Yarn: 30 | 31 | ``` 32 | $ yarn global add cvep 33 | ``` 34 | 35 | Then follow the prompts! 36 | 37 | ``` 38 | cvep //Follow the flow 39 | ``` 40 | 41 | or 42 | 43 | ``` 44 | cvep - 45 | ``` 46 | 47 | | arg | description | 48 | | ------------------ | ----------------------------------- | 49 | | -J \| --javascript | Based on Javascript create Project | 50 | | -T \| --typescript | Based on typescript create Project | 51 | 52 | At this point the project is created! 53 | 54 | ## >Dev And Build 55 | 56 | Use ViteConfig to build and develop the VUE !!! 57 | 58 | Use the PackageJson Build attribute to package the project !!! 59 | 60 | With NPM: 61 | 62 | ``` 63 | $ npm run dev 64 | $ npm run build 65 | ``` 66 | 67 | With Yarn: 68 | 69 | ``` 70 | $ yarn dev 71 | $ yarn build 72 | ``` 73 | 74 | -------------------------------------------------------------------------------- /bin/create-vite-electron.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../dist/index') -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cvep", 3 | "version": "1.0.2", 4 | "author": "wwog", 5 | "license": "MIT", 6 | "keywords": [ 7 | "vue3", 8 | "vite", 9 | "electron", 10 | "projectCreate", 11 | "fast", 12 | "dev", 13 | "build" 14 | ], 15 | "description": "Create the Electron project based on the next generation development server vite and vue3.", 16 | "homepage": "https://github.com/wwog/cvep", 17 | "repository": "wwog/cvep", 18 | "bugs": { 19 | "url": "https://github.com/wwog/cvep/issues" 20 | }, 21 | "bin": { 22 | "cvep": "bin/create-vite-electron.js" 23 | }, 24 | "files": [ 25 | "bin", 26 | "dist", 27 | "template-js", 28 | "template-ts" 29 | ], 30 | "engines": { 31 | "node": ">=12.0.0" 32 | }, 33 | "scripts": { 34 | "build": "node scripts/build.js && tsc" 35 | }, 36 | "dependencies": { 37 | "esbuild": "^0.8.46", 38 | "@vitejs/create-app": "^2.0.0", 39 | "enquirer": "^2.3.6", 40 | "minimist": "^1.2.5" 41 | }, 42 | "devDependencies": { 43 | "@types/minimist": "^1.2.1", 44 | "@types/node": "^14.14.25", 45 | "typescript": "^4.1.5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /project-script/base.ts: -------------------------------------------------------------------------------- 1 | import { resolveConfig } from 'vite' 2 | import * as fs from 'fs' 3 | import { join } from 'path' 4 | import { buildSync } from 'esbuild' 5 | import * as chalk from 'chalk' 6 | 7 | export class Base { 8 | protected readonly root = join(__dirname, '../') 9 | protected readonly pkg = join(this.root, 'package.json') 10 | protected readonly main = join(this.root, 'src/main') 11 | protected readonly render = join(this.root, 'src/render') 12 | protected outDir = join(this.root, 'dist') 13 | protected mainEntrySrc = ''; 14 | protected mainEntry = ''; 15 | protected readonly outFlag = { 16 | vite: chalk.green(`[vite] `), 17 | electron: chalk.blue(`[electron] `), 18 | control: chalk.rgb(190, 33, 64)(`[control] `) 19 | } 20 | protected async init() { 21 | let pkg 22 | try { 23 | pkg = require(this.pkg) 24 | }catch(error){ 25 | console.error(error) 26 | process.exit(); 27 | } 28 | //main入口,首先根据pkgJson为主,如果没有则在main目录寻找entry文件 29 | if (pkg.main) { 30 | this.mainEntrySrc = join(this.root, pkg.main) 31 | } else { 32 | let isFind = false 33 | for (const file of fs.readdirSync(this.main)) { 34 | if (file.startsWith('entry')) { 35 | isFind = true; 36 | this.mainEntrySrc = join(this.main, file) 37 | break 38 | } 39 | } 40 | if(!isFind){ 41 | console.error(new Error(`not find electron mainProcess Entry file!!!`)) 42 | } 43 | } 44 | //outDir,vite默认为dist 45 | const viteConfig = await resolveConfig({}, 'build') 46 | this.outDir = viteConfig.build.outDir 47 | //mainOut 48 | this.mainEntry = join(this.outDir, '__entry.js') 49 | } 50 | protected buildMain(isDev = true) { 51 | if (isDev) process.env.CVE_ENV = 'development' 52 | return buildSync({ 53 | bundle: true, 54 | entryPoints: [this.mainEntrySrc], 55 | external: ['electron'], 56 | platform: 'node', 57 | minify: isDev ? false : true, 58 | outfile: this.mainEntry 59 | }) 60 | } 61 | } 62 | export function isObject(arg: any) { 63 | return arg !== null && Object.prototype.toString.call(arg) === '[object Object]' 64 | } 65 | 66 | export function isArray(arg: any) { 67 | return Array.isArray(arg) 68 | } 69 | 70 | export function mergeObject(target: any, ...arg: any[]) { 71 | return arg.reduce((acc, cur) => { 72 | return Object.keys(cur).reduce((subAcc, key) => { 73 | const srcVal = cur[key] 74 | if (isObject(srcVal)) { 75 | subAcc[key] = mergeObject(subAcc[key] ? subAcc[key] : {}, srcVal) 76 | } else if (isArray(srcVal)) { 77 | subAcc[key] = srcVal.map((item: any, idx: number) => { 78 | if (isObject(item)) { 79 | const curAccVal = subAcc[key] ? subAcc[key] : [] 80 | return mergeObject(curAccVal[idx] ? curAccVal[idx] : {}, item) 81 | } else { 82 | return item 83 | } 84 | }) 85 | } else { 86 | subAcc[key] = srcVal 87 | } 88 | return subAcc 89 | }, acc) 90 | }, target) 91 | } -------------------------------------------------------------------------------- /project-script/build.ts: -------------------------------------------------------------------------------- 1 | import { Base } from "./base"; 2 | import { build as viteBuild } from "vite"; 3 | import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs"; 4 | import { build as electronBuild } from "electron-builder"; 5 | import { join } from "path"; 6 | import * as chalk from 'chalk' 7 | 8 | export class Build extends Base { 9 | private async buildRender() { 10 | await viteBuild() 11 | } 12 | private getMIRROR() { 13 | //npmrc ELECTRON_MIRROR 14 | let ELECTRON_MIRROR 15 | let npmrc = join(this.root, '.npmrc') 16 | if (existsSync(npmrc)) { 17 | ELECTRON_MIRROR = readFileSync(npmrc).toString() 18 | let _t = ELECTRON_MIRROR.split(/[(\r\n)\r\n]+/) 19 | for (let i = 0; i < _t.length; i++) { 20 | let key = _t[i].split('=') 21 | if (key[0] == 'ELECTRON_MIRROR') { 22 | ELECTRON_MIRROR = key[1] 23 | break; 24 | } 25 | } 26 | } 27 | return ELECTRON_MIRROR 28 | } 29 | private async buildProject() { 30 | mkdirSync(join(this.outDir, "node_modules")); 31 | let MIRROR = this.getMIRROR() 32 | await electronBuild({ 33 | projectDir: this.outDir, 34 | config: { 35 | directories: { 36 | output: join(this.root, "release"), 37 | }, 38 | extends: null, 39 | electronDownload: { 40 | mirror: MIRROR 41 | }, 42 | } 43 | }) 44 | } 45 | private mkPkgJson() { 46 | let pkg 47 | try { 48 | pkg = require(this.pkg); 49 | } catch (error) { 50 | console.error(error) 51 | process.exit() 52 | } 53 | 54 | if (!pkg.main) { 55 | pkg.main = '__entry.js' 56 | } 57 | let electronVersion = pkg.devDependencies.electron.replace("^", ""); 58 | delete pkg.scripts; 59 | delete pkg.dependencies; 60 | delete pkg.devDependencies; 61 | pkg.devDependencies = { electron: electronVersion }; 62 | writeFileSync( 63 | join(this.outDir, "package.json"), 64 | JSON.stringify(pkg, null, 2), 65 | ); 66 | return pkg; 67 | } 68 | async run() { 69 | await this.init().catch(e => { 70 | console.error(chalk.red`[contorlError] init failed:\n${e}`) 71 | }) 72 | await this.buildRender() 73 | this.buildMain() 74 | this.mkPkgJson() 75 | await this.buildProject().catch(e => { 76 | console.error(chalk.red`[electronBuilderError] \n${e}`) 77 | process.exit() 78 | }) 79 | console.log(this.outFlag.control + ` The packaged application is complete`) 80 | } 81 | } -------------------------------------------------------------------------------- /project-script/dev.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './base' 2 | import { resolveConfig, createServer, ViteDevServer } from 'vite' 3 | import * as chalk from 'chalk' 4 | import { existsSync, rmdirSync } from 'fs' 5 | import { spawn, ChildProcessWithoutNullStreams } from "child_process"; 6 | import { watch } from "chokidar"; 7 | import { Server } from 'http'; 8 | 9 | export class Dev extends Base { 10 | private port!: number; 11 | private viteServer!: ViteDevServer; 12 | private electron!: ChildProcessWithoutNullStreams; 13 | private isReStart = false 14 | private oldTime!: number; 15 | private async runRender() { 16 | let config = await resolveConfig({}, 'serve') 17 | this.port = config.server.port ? config.server.port : 3000 18 | this.viteServer = await createServer({ 19 | server: { port: this.port } 20 | }) as ViteDevServer 21 | let httpServer = this.viteServer.httpServer as Server 22 | if (httpServer != null) { 23 | return new Promise(resolve => { 24 | const onError = (e: Error & { code?: string }) => { 25 | if (e.code === 'EADDRINUSE') { 26 | if (config.server.strictPort) { 27 | console.error(chalk.red`[viteError] Port ${this.port} is already in use`) 28 | process.exit() 29 | } else { 30 | console.log(this.outFlag.vite + `Port ${this.port} is in use, trying another one...`); 31 | httpServer.listen(++this.port) 32 | } 33 | } else { 34 | console.error(chalk.red`[viteError] ${e}`) 35 | process.exit() 36 | } 37 | } 38 | httpServer.on('error', onError) 39 | httpServer.listen(this.port, () => { 40 | httpServer.removeListener('error', onError) 41 | console.log(this.outFlag.vite + `Dev server running at:${this.port}`) 42 | resolve('') 43 | }) 44 | }) 45 | } else { 46 | console.error(chalk.red`[viteError] httpServer Create a failure`) 47 | } 48 | } 49 | private async runElectron() { 50 | await this.init().catch(e => { 51 | console.error(chalk.red`[contorlError] init failed:\n${e}`) 52 | }) 53 | this.buildMain() 54 | let _electron 55 | try { 56 | _electron = require('electron') 57 | } catch (error) { 58 | console.error(error) 59 | process.exit(); 60 | } 61 | this.electron = spawn(_electron.toString(), [this.mainEntry]) 62 | this.electron.on('close', () => { 63 | if (this.isReStart) { 64 | this.isReStart = false 65 | this.runElectron() 66 | } else { 67 | this.viteServer.close() 68 | process.exit() 69 | } 70 | }) 71 | process.on('exit', () => { 72 | if (existsSync(this.outDir)) { 73 | rmdirSync(this.outDir, { recursive: true }) 74 | } 75 | }) 76 | this.electron.stdout.on('data', chunk => { 77 | let data = chunk.toString() as string 78 | if (data.trim().length > 0) { 79 | console.log(this.outFlag.electron + `${chunk.toString()}`) 80 | } 81 | }) 82 | console.log(this.outFlag.control + `Start time: ${Date.now() - this.oldTime}ms`) 83 | } 84 | private mainHMR() { 85 | watch(this.main).on('change', () => { 86 | console.log(this.outFlag.control + `entry file change.restart at ${new Date()}`) 87 | this.isReStart = true 88 | this.electron.kill() 89 | this.oldTime = Date.now() 90 | }) 91 | } 92 | async run() { 93 | this.oldTime = Date.now() 94 | await this.runRender() 95 | process.env.CVE_PORT = this.port + '' 96 | await this.runElectron() 97 | this.mainHMR() 98 | } 99 | } 100 | //test 101 | //new Dev().run() -------------------------------------------------------------------------------- /project-script/index.ts: -------------------------------------------------------------------------------- 1 | import { Dev } from './dev' 2 | import { Build } from './build' 3 | let method = process.argv.slice(2)[0] 4 | async function init() { 5 | switch (method) { 6 | case 'build': 7 | await new Build().run() 8 | break; 9 | case 'dev': 10 | await new Dev().run() 11 | break; 12 | default: 13 | console.log(`The argument should be dev or build`) 14 | } 15 | } 16 | 17 | init().catch(e => { 18 | console.log(e) 19 | }) -------------------------------------------------------------------------------- /project-script/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | "lib": ["ESNext"], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | // "outDir": "./", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | 44 | /* Module Resolution Options */ 45 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": [], /* Type declaration files to be included in compilation. */ 51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 52 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 55 | 56 | /* Source Map Options */ 57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 61 | 62 | /* Experimental Options */ 63 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 65 | 66 | /* Advanced Options */ 67 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 68 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const { buildSync } = require('esbuild') 2 | const path = require('path') 3 | const fs = require('fs') 4 | 5 | let root = path.join(__dirname, '../') 6 | 7 | function buildProjectScript() { 8 | let templateJs = path.join(root, 'template-js/scripts/project.js') 9 | let result = buildSync({ 10 | platform: 'node', 11 | bundle: true, 12 | entryPoints: [path.join(root, 'project-script/index.ts')], 13 | treeShaking: 'ignore-annotations', 14 | external: [ 15 | 'vite', 'chalk', 'fs', 'child_process', 16 | 'chokidar', 'http', 'electron-builder', 'path', 17 | 'esbuild' 18 | ], 19 | outfile: templateJs, 20 | minify: false 21 | }) 22 | if (result.warnings.length === 0) { 23 | fs.copyFileSync(templateJs, templateJs.replace('-js', '-ts')) 24 | return true 25 | } 26 | return false 27 | } 28 | 29 | buildProjectScript() -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Project } from "./lib/project"; 2 | import { prompt } from "enquirer" 3 | import * as parseArgv from "minimist" 4 | import * as fs from 'fs' 5 | 6 | import { emptyDir } from './lib/utils' 7 | let argv = parseArgv.default(process.argv.slice(2), { 8 | alias: { 9 | j: ['J', 'javascript'], 10 | t: ['T', 'typescript'], 11 | } 12 | }) 13 | const TEMPLATES = [ 14 | 'ts', 15 | 'js', 16 | ] 17 | 18 | async function init() { 19 | let projectName = argv._[0] 20 | if (!projectName) { 21 | const { name }: any = await prompt({ 22 | type: 'input', 23 | name: 'name', 24 | message: `Project name:`, 25 | initial: 'electronApp' 26 | }) 27 | projectName = name 28 | } 29 | let project = new Project(projectName) 30 | type _t = 'js' | 'ts' | undefined 31 | let template: _t = argv.j ? 'js' : argv.t ? 'ts' : undefined 32 | if (!template) { 33 | const { t }: any = await prompt({ 34 | type: 'select', 35 | name: 't', 36 | message: `Select a template:`, 37 | choices: TEMPLATES 38 | }) 39 | template = t 40 | } 41 | await project.create(template, async () => { 42 | const existing = fs.readdirSync(project.root) 43 | if (existing.length) { 44 | const { yes }: any = await prompt({ 45 | type: 'confirm', 46 | name: 'yes', 47 | initial: 'Y', 48 | message: 49 | `Target directory ${projectName} is not empty.\n` + 50 | `Remove existing files and continue?` 51 | }) 52 | if (yes) { 53 | emptyDir(project.root) 54 | } else { 55 | return 56 | } 57 | } 58 | }) 59 | const { addItems }: { addItems: any[] } = await prompt({ 60 | type: 'multiselect', 61 | name: 'addItems', 62 | message: 'Select addItems:', 63 | initial: 0, 64 | choices: Project.ADDITEMS 65 | }) 66 | project.addItem(addItems) 67 | console.log(`\n\n cd ${projectName}\n yarn\n`) 68 | } 69 | 70 | init().then(value => { 71 | 72 | }).catch(e => { console.log(e) }) -------------------------------------------------------------------------------- /src/lib/project.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as fs from 'fs'; 3 | import { copy, emptyDir } from './utils'; 4 | import { join } from "path"; 5 | let cwd = process.cwd(); 6 | export class Project { 7 | public root: string; 8 | private viteTemplate!: string; 9 | protected packagePath: string; 10 | protected createAppDir = path.join(__dirname, '../../node_modules', '@vitejs/create-app') 11 | protected templateDir = ''; 12 | static ADDITEMS = [ 13 | 'npmrc', 14 | ] 15 | constructor(public projectName: string) { 16 | this.root = join(cwd, projectName) 17 | this.packagePath = join(this.root, 'package.json') 18 | } 19 | async create(language: 'js' | 'ts' = 'ts', existHandle: any) { 20 | let dirName = '' 21 | if (language == 'js') { 22 | dirName = 'template-vue' 23 | } else { 24 | dirName = 'template-vue-ts' 25 | } 26 | if (!fs.existsSync(this.root)) { 27 | fs.mkdirSync(this.root, { recursive: true }) 28 | } else { 29 | await existHandle() 30 | } 31 | //将vite模板输出文件夹 32 | this.viteTemplate = path.join(this.createAppDir, dirName) 33 | let files = fs.readdirSync(this.viteTemplate) 34 | for (const file of files) { 35 | this.write(file) 36 | } 37 | this.cleanUnwantedFiles() 38 | this.engineering() 39 | //将项目模板输出到文件夹 40 | this.templateDir = path.join(__dirname, `../../template-${language}`) 41 | files = fs.readdirSync(this.templateDir) 42 | for (const file of files) { 43 | this.write(file, undefined, this.templateDir) 44 | } 45 | //重写pkg 46 | let pkgData = require(this.packagePath) 47 | pkgData.name = this.projectName.toLowerCase() 48 | pkgData.devDependencies['electron'] = '^11.2.3' 49 | pkgData.devDependencies['chalk'] = '^4.1.0' 50 | pkgData.devDependencies['esbuild'] = '^0.8.34' 51 | pkgData.devDependencies['electron-builder'] = '^22.9.1' 52 | pkgData.devDependencies['chokidar'] = '^3.5.1' 53 | pkgData.scripts['vite_dev']=pkgData.scripts['dev'] 54 | pkgData.scripts['vite_build']=pkgData.scripts['build'] 55 | pkgData.scripts['dev'] = "node ./scripts/project.js dev" 56 | pkgData.scripts['build'] = "node ./scripts/project.js build" 57 | fs.writeFileSync(this.packagePath, JSON.stringify(pkgData, null, 2)) 58 | } 59 | addItem(itemArr: string[]) { 60 | for (const item of itemArr) { 61 | //@ts-ignore 62 | if (this[item]) { 63 | //@ts-ignore 64 | this[item]() 65 | } 66 | } 67 | } 68 | private write(file: string, content: any = undefined, templateDir = this.viteTemplate) { 69 | //@ts-ignore 70 | const targetPath = renameFiles[file] 71 | //@ts-ignore 72 | ? path.join(this.root, renameFiles[file]) 73 | : path.join(this.root, file) 74 | if (content) { 75 | fs.writeFileSync(targetPath, content) 76 | } else { 77 | copy(path.join(templateDir, file), targetPath) 78 | } 79 | } 80 | private cleanUnwantedFiles() { 81 | fs.unlinkSync(join(this.root, 'index.html')) 82 | emptyDir(join(this.root, 'public')) 83 | fs.rmdirSync(join(this.root, 'public')) 84 | } 85 | private engineering() { 86 | let oldPath = join(this.root, 'src') 87 | let tempPath = join(this.root, 'render') 88 | let newPath = join(this.root, 'src/render') 89 | fs.renameSync(oldPath, tempPath) 90 | fs.mkdirSync(oldPath) 91 | fs.renameSync(tempPath, newPath) 92 | } 93 | private npmrc() { 94 | let fileData = 'ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/\nELECTRON_BUILDER_BINARIES_MIRROR=http://npm.taobao.org/mirrors/electron-builder-binaries/' 95 | this.write('.npmrc', fileData) 96 | } 97 | private elementPlus() { 98 | let pkgData = require(this.packagePath) 99 | pkgData.dependencies['element-plus'] = 'latest' 100 | fs.writeFileSync(this.packagePath, JSON.stringify(pkgData, null, 2)) 101 | } 102 | } 103 | const renameFiles = { 104 | _gitignore: '.gitignore' 105 | } -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as fs from 'fs' 3 | export function copy(src:string, dest:string) { 4 | const stat = fs.statSync(src) 5 | if (stat.isDirectory()) { 6 | copyDir(src, dest) 7 | } else { 8 | fs.copyFileSync(src, dest) 9 | } 10 | } 11 | 12 | export function copyDir(srcDir:string, destDir:string) { 13 | fs.mkdirSync(destDir, { recursive: true }) 14 | for (const file of fs.readdirSync(srcDir)) { 15 | const srcFile = path.resolve(srcDir, file) 16 | const destFile = path.resolve(destDir, file) 17 | copy(srcFile, destFile) 18 | } 19 | } 20 | 21 | export function emptyDir(dir:string) { 22 | if (!fs.existsSync(dir)) { 23 | return 24 | } 25 | for (const file of fs.readdirSync(dir)) { 26 | const abs = path.resolve(dir, file) 27 | // baseline is Node 12 so can't use rmSync :( 28 | if (fs.lstatSync(abs).isDirectory()) { 29 | emptyDir(abs) 30 | fs.rmdirSync(abs) 31 | } else { 32 | fs.unlinkSync(abs) 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /template-js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | newApp 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /template-js/scripts/project.js: -------------------------------------------------------------------------------- 1 | var __create = Object.create; 2 | var __defProp = Object.defineProperty; 3 | var __getProtoOf = Object.getPrototypeOf; 4 | var __hasOwnProp = Object.prototype.hasOwnProperty; 5 | var __getOwnPropNames = Object.getOwnPropertyNames; 6 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 7 | var __markAsModule = (target) => __defProp(target, "__esModule", {value: true}); 8 | var __exportStar = (target, module2, desc) => { 9 | if (module2 && typeof module2 === "object" || typeof module2 === "function") { 10 | for (let key of __getOwnPropNames(module2)) 11 | if (!__hasOwnProp.call(target, key) && key !== "default") 12 | __defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable}); 13 | } 14 | return target; 15 | }; 16 | var __toModule = (module2) => { 17 | if (module2 && module2.__esModule) 18 | return module2; 19 | return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", {value: module2, enumerable: true})), module2); 20 | }; 21 | 22 | // project-script/base.ts 23 | var import_vite = __toModule(require("vite")); 24 | var fs = __toModule(require("fs")); 25 | var import_path = __toModule(require("path")); 26 | var import_esbuild = __toModule(require("esbuild")); 27 | var chalk = __toModule(require("chalk")); 28 | var Base = class { 29 | constructor() { 30 | this.root = import_path.join(__dirname, "../"); 31 | this.pkg = import_path.join(this.root, "package.json"); 32 | this.main = import_path.join(this.root, "src/main"); 33 | this.render = import_path.join(this.root, "src/render"); 34 | this.outDir = import_path.join(this.root, "dist"); 35 | this.mainEntrySrc = ""; 36 | this.mainEntry = ""; 37 | this.outFlag = { 38 | vite: chalk.green(`[vite] `), 39 | electron: chalk.blue(`[electron] `), 40 | control: chalk.rgb(190, 33, 64)(`[control] `) 41 | }; 42 | } 43 | async init() { 44 | let pkg; 45 | try { 46 | pkg = require(this.pkg); 47 | } catch (error) { 48 | console.error(error); 49 | process.exit(); 50 | } 51 | if (pkg.main) { 52 | this.mainEntrySrc = import_path.join(this.root, pkg.main); 53 | } else { 54 | let isFind = false; 55 | for (const file of fs.readdirSync(this.main)) { 56 | if (file.startsWith("entry")) { 57 | isFind = true; 58 | this.mainEntrySrc = import_path.join(this.main, file); 59 | break; 60 | } 61 | } 62 | if (!isFind) { 63 | console.error(new Error(`not find electron mainProcess Entry file!!!`)); 64 | } 65 | } 66 | const viteConfig = await import_vite.resolveConfig({}, "build"); 67 | this.outDir = viteConfig.build.outDir; 68 | this.mainEntry = import_path.join(this.outDir, "__entry.js"); 69 | } 70 | buildMain(isDev = true) { 71 | if (isDev) 72 | process.env.CVE_ENV = "development"; 73 | return import_esbuild.buildSync({ 74 | bundle: true, 75 | entryPoints: [this.mainEntrySrc], 76 | external: ["electron"], 77 | platform: "node", 78 | minify: isDev ? false : true, 79 | outfile: this.mainEntry 80 | }); 81 | } 82 | }; 83 | 84 | // project-script/dev.ts 85 | var import_vite2 = __toModule(require("vite")); 86 | var chalk2 = __toModule(require("chalk")); 87 | var import_fs = __toModule(require("fs")); 88 | var import_child_process = __toModule(require("child_process")); 89 | var import_chokidar = __toModule(require("chokidar")); 90 | var Dev = class extends Base { 91 | constructor() { 92 | super(...arguments); 93 | this.isReStart = false; 94 | } 95 | async runRender() { 96 | let config = await import_vite2.resolveConfig({}, "serve"); 97 | this.port = config.server.port ? config.server.port : 3e3; 98 | this.viteServer = await import_vite2.createServer({ 99 | server: {port: this.port} 100 | }); 101 | let httpServer = this.viteServer.httpServer; 102 | if (httpServer != null) { 103 | return new Promise((resolve) => { 104 | const onError = (e) => { 105 | if (e.code === "EADDRINUSE") { 106 | if (config.server.strictPort) { 107 | console.error(chalk2.red`[viteError] Port ${this.port} is already in use`); 108 | process.exit(); 109 | } else { 110 | console.log(this.outFlag.vite + `Port ${this.port} is in use, trying another one...`); 111 | httpServer.listen(++this.port); 112 | } 113 | } else { 114 | console.error(chalk2.red`[viteError] ${e}`); 115 | process.exit(); 116 | } 117 | }; 118 | httpServer.on("error", onError); 119 | httpServer.listen(this.port, () => { 120 | httpServer.removeListener("error", onError); 121 | console.log(this.outFlag.vite + `Dev server running at:${this.port}`); 122 | resolve(""); 123 | }); 124 | }); 125 | } else { 126 | console.error(chalk2.red`[viteError] httpServer Create a failure`); 127 | } 128 | } 129 | async runElectron() { 130 | await this.init().catch((e) => { 131 | console.error(chalk2.red`[contorlError] init failed:\n${e}`); 132 | }); 133 | this.buildMain(); 134 | let _electron; 135 | try { 136 | _electron = require("electron"); 137 | } catch (error) { 138 | console.error(error); 139 | process.exit(); 140 | } 141 | this.electron = import_child_process.spawn(_electron.toString(), [this.mainEntry]); 142 | this.electron.on("close", () => { 143 | if (this.isReStart) { 144 | this.isReStart = false; 145 | this.runElectron(); 146 | } else { 147 | this.viteServer.close(); 148 | process.exit(); 149 | } 150 | }); 151 | process.on("exit", () => { 152 | if (import_fs.existsSync(this.outDir)) { 153 | import_fs.rmdirSync(this.outDir, {recursive: true}); 154 | } 155 | }); 156 | this.electron.stdout.on("data", (chunk) => { 157 | let data = chunk.toString(); 158 | if (data.trim().length > 0) { 159 | console.log(this.outFlag.electron + `${chunk.toString()}`); 160 | } 161 | }); 162 | console.log(this.outFlag.control + `Start time: ${Date.now() - this.oldTime}ms`); 163 | } 164 | mainHMR() { 165 | import_chokidar.watch(this.main).on("change", () => { 166 | console.log(this.outFlag.control + `entry file change.restart at ${new Date()}`); 167 | this.isReStart = true; 168 | this.electron.kill(); 169 | this.oldTime = Date.now(); 170 | }); 171 | } 172 | async run() { 173 | this.oldTime = Date.now(); 174 | await this.runRender(); 175 | process.env.CVE_PORT = this.port + ""; 176 | await this.runElectron(); 177 | this.mainHMR(); 178 | } 179 | }; 180 | 181 | // project-script/build.ts 182 | var import_vite3 = __toModule(require("vite")); 183 | var import_fs2 = __toModule(require("fs")); 184 | var import_electron_builder = __toModule(require("electron-builder")); 185 | var import_path2 = __toModule(require("path")); 186 | var chalk3 = __toModule(require("chalk")); 187 | var Build = class extends Base { 188 | async buildRender() { 189 | await import_vite3.build(); 190 | } 191 | getMIRROR() { 192 | let ELECTRON_MIRROR; 193 | let npmrc = import_path2.join(this.root, ".npmrc"); 194 | if (import_fs2.existsSync(npmrc)) { 195 | ELECTRON_MIRROR = import_fs2.readFileSync(npmrc).toString(); 196 | let _t = ELECTRON_MIRROR.split(/[(\r\n)\r\n]+/); 197 | for (let i = 0; i < _t.length; i++) { 198 | let key = _t[i].split("="); 199 | if (key[0] == "ELECTRON_MIRROR") { 200 | ELECTRON_MIRROR = key[1]; 201 | break; 202 | } 203 | } 204 | } 205 | return ELECTRON_MIRROR; 206 | } 207 | async buildProject() { 208 | import_fs2.mkdirSync(import_path2.join(this.outDir, "node_modules")); 209 | let MIRROR = this.getMIRROR(); 210 | await import_electron_builder.build({ 211 | projectDir: this.outDir, 212 | config: { 213 | directories: { 214 | output: import_path2.join(this.root, "release") 215 | }, 216 | extends: null, 217 | electronDownload: { 218 | mirror: MIRROR 219 | } 220 | } 221 | }); 222 | } 223 | mkPkgJson() { 224 | let pkg; 225 | try { 226 | pkg = require(this.pkg); 227 | } catch (error) { 228 | console.error(error); 229 | process.exit(); 230 | } 231 | if (!pkg.main) { 232 | pkg.main = "__entry.js"; 233 | } 234 | let electronVersion = pkg.devDependencies.electron.replace("^", ""); 235 | delete pkg.scripts; 236 | delete pkg.dependencies; 237 | delete pkg.devDependencies; 238 | pkg.devDependencies = {electron: electronVersion}; 239 | import_fs2.writeFileSync(import_path2.join(this.outDir, "package.json"), JSON.stringify(pkg, null, 2)); 240 | return pkg; 241 | } 242 | async run() { 243 | await this.init().catch((e) => { 244 | console.error(chalk3.red`[contorlError] init failed:\n${e}`); 245 | }); 246 | await this.buildRender(); 247 | this.buildMain(); 248 | this.mkPkgJson(); 249 | await this.buildProject().catch((e) => { 250 | console.error(chalk3.red`[electronBuilderError] \n${e}`); 251 | process.exit(); 252 | }); 253 | console.log(this.outFlag.control + ` The packaged application is complete`); 254 | } 255 | }; 256 | 257 | // project-script/index.ts 258 | var method = process.argv.slice(2)[0]; 259 | async function init() { 260 | switch (method) { 261 | case "build": 262 | await new Build().run(); 263 | break; 264 | case "dev": 265 | await new Dev().run(); 266 | break; 267 | default: 268 | console.log(`The argument should be dev or build`); 269 | } 270 | } 271 | init().catch((e) => { 272 | console.log(e); 273 | }); 274 | -------------------------------------------------------------------------------- /template-js/src/main/entry.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow } = require("electron"); 2 | const createProtocol = require("./protocol"); 3 | const isDevelopment = process.env.CVE_ENV === 'development' 4 | 5 | 6 | app.on('ready', () => { 7 | let win = new BrowserWindow({ 8 | show: false, 9 | webPreferences: { 10 | nodeIntegration: true 11 | } 12 | }) 13 | win.on('ready-to-show', () => { win.show() }) 14 | if (isDevelopment) { 15 | win.loadURL(`http://localhost:${process.env.CVE_PORT}`) 16 | } else { 17 | createProtocol('app') 18 | win.loadURL('app://./index.html') 19 | } 20 | win.webContents.openDevTools() 21 | }) 22 | -------------------------------------------------------------------------------- /template-js/src/main/protocol.js: -------------------------------------------------------------------------------- 1 | const { protocol } = require("electron"); 2 | const { join, extname } = require("path"); 3 | const { readFile } = require("fs"); 4 | const { URL } = require("url"); 5 | protocol.registerSchemesAsPrivileged([ 6 | { scheme: "app", privileges: { secure: true, standard: true } }, 7 | ]); 8 | module.exports = (scheme) => { 9 | protocol.registerBufferProtocol(scheme, (req, res) => { 10 | let pathName = new URL(req.url).pathname; 11 | pathName = decodeURI(pathName); 12 | readFile(join(__dirname, pathName), (e, data) => { 13 | if (e) { 14 | console.error(`Failed to read ${pathName} on ${scheme} protocol`); 15 | } 16 | const extension = extname(pathName).toLowerCase(); 17 | let mimeType = ""; 18 | switch (extension) { 19 | case ".js": 20 | mimeType = "text/javascript"; 21 | break; 22 | case ".html": 23 | mimeType = "text/html"; 24 | break; 25 | case ".css": 26 | mimeType = "text/css"; 27 | break; 28 | case ".svg": 29 | case ".svgz": 30 | mimeType = "image/svg+xml"; 31 | break; 32 | case ".json": 33 | mimeType = "application/json"; 34 | break; 35 | case ".wasm": 36 | mimeType = "application/wasm"; 37 | } 38 | res({ mimeType, data }); 39 | }); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /template-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | newApp 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /template-ts/scripts/project.js: -------------------------------------------------------------------------------- 1 | var __create = Object.create; 2 | var __defProp = Object.defineProperty; 3 | var __getProtoOf = Object.getPrototypeOf; 4 | var __hasOwnProp = Object.prototype.hasOwnProperty; 5 | var __getOwnPropNames = Object.getOwnPropertyNames; 6 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 7 | var __markAsModule = (target) => __defProp(target, "__esModule", {value: true}); 8 | var __exportStar = (target, module2, desc) => { 9 | if (module2 && typeof module2 === "object" || typeof module2 === "function") { 10 | for (let key of __getOwnPropNames(module2)) 11 | if (!__hasOwnProp.call(target, key) && key !== "default") 12 | __defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable}); 13 | } 14 | return target; 15 | }; 16 | var __toModule = (module2) => { 17 | if (module2 && module2.__esModule) 18 | return module2; 19 | return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", {value: module2, enumerable: true})), module2); 20 | }; 21 | 22 | // project-script/base.ts 23 | var import_vite = __toModule(require("vite")); 24 | var fs = __toModule(require("fs")); 25 | var import_path = __toModule(require("path")); 26 | var import_esbuild = __toModule(require("esbuild")); 27 | var chalk = __toModule(require("chalk")); 28 | var Base = class { 29 | constructor() { 30 | this.root = import_path.join(__dirname, "../"); 31 | this.pkg = import_path.join(this.root, "package.json"); 32 | this.main = import_path.join(this.root, "src/main"); 33 | this.render = import_path.join(this.root, "src/render"); 34 | this.outDir = import_path.join(this.root, "dist"); 35 | this.mainEntrySrc = ""; 36 | this.mainEntry = ""; 37 | this.outFlag = { 38 | vite: chalk.green(`[vite] `), 39 | electron: chalk.blue(`[electron] `), 40 | control: chalk.rgb(190, 33, 64)(`[control] `) 41 | }; 42 | } 43 | async init() { 44 | let pkg; 45 | try { 46 | pkg = require(this.pkg); 47 | } catch (error) { 48 | console.error(error); 49 | process.exit(); 50 | } 51 | if (pkg.main) { 52 | this.mainEntrySrc = import_path.join(this.root, pkg.main); 53 | } else { 54 | let isFind = false; 55 | for (const file of fs.readdirSync(this.main)) { 56 | if (file.startsWith("entry")) { 57 | isFind = true; 58 | this.mainEntrySrc = import_path.join(this.main, file); 59 | break; 60 | } 61 | } 62 | if (!isFind) { 63 | console.error(new Error(`not find electron mainProcess Entry file!!!`)); 64 | } 65 | } 66 | const viteConfig = await import_vite.resolveConfig({}, "build"); 67 | this.outDir = viteConfig.build.outDir; 68 | this.mainEntry = import_path.join(this.outDir, "__entry.js"); 69 | } 70 | buildMain(isDev = true) { 71 | if (isDev) 72 | process.env.CVE_ENV = "development"; 73 | return import_esbuild.buildSync({ 74 | bundle: true, 75 | entryPoints: [this.mainEntrySrc], 76 | external: ["electron"], 77 | platform: "node", 78 | minify: isDev ? false : true, 79 | outfile: this.mainEntry 80 | }); 81 | } 82 | }; 83 | 84 | // project-script/dev.ts 85 | var import_vite2 = __toModule(require("vite")); 86 | var chalk2 = __toModule(require("chalk")); 87 | var import_fs = __toModule(require("fs")); 88 | var import_child_process = __toModule(require("child_process")); 89 | var import_chokidar = __toModule(require("chokidar")); 90 | var Dev = class extends Base { 91 | constructor() { 92 | super(...arguments); 93 | this.isReStart = false; 94 | } 95 | async runRender() { 96 | let config = await import_vite2.resolveConfig({}, "serve"); 97 | this.port = config.server.port ? config.server.port : 3e3; 98 | this.viteServer = await import_vite2.createServer({ 99 | server: {port: this.port} 100 | }); 101 | let httpServer = this.viteServer.httpServer; 102 | if (httpServer != null) { 103 | return new Promise((resolve) => { 104 | const onError = (e) => { 105 | if (e.code === "EADDRINUSE") { 106 | if (config.server.strictPort) { 107 | console.error(chalk2.red`[viteError] Port ${this.port} is already in use`); 108 | process.exit(); 109 | } else { 110 | console.log(this.outFlag.vite + `Port ${this.port} is in use, trying another one...`); 111 | httpServer.listen(++this.port); 112 | } 113 | } else { 114 | console.error(chalk2.red`[viteError] ${e}`); 115 | process.exit(); 116 | } 117 | }; 118 | httpServer.on("error", onError); 119 | httpServer.listen(this.port, () => { 120 | httpServer.removeListener("error", onError); 121 | console.log(this.outFlag.vite + `Dev server running at:${this.port}`); 122 | resolve(""); 123 | }); 124 | }); 125 | } else { 126 | console.error(chalk2.red`[viteError] httpServer Create a failure`); 127 | } 128 | } 129 | async runElectron() { 130 | await this.init().catch((e) => { 131 | console.error(chalk2.red`[contorlError] init failed:\n${e}`); 132 | }); 133 | this.buildMain(); 134 | let _electron; 135 | try { 136 | _electron = require("electron"); 137 | } catch (error) { 138 | console.error(error); 139 | process.exit(); 140 | } 141 | this.electron = import_child_process.spawn(_electron.toString(), [this.mainEntry]); 142 | this.electron.on("close", () => { 143 | if (this.isReStart) { 144 | this.isReStart = false; 145 | this.runElectron(); 146 | } else { 147 | this.viteServer.close(); 148 | process.exit(); 149 | } 150 | }); 151 | process.on("exit", () => { 152 | if (import_fs.existsSync(this.outDir)) { 153 | import_fs.rmdirSync(this.outDir, {recursive: true}); 154 | } 155 | }); 156 | this.electron.stdout.on("data", (chunk) => { 157 | let data = chunk.toString(); 158 | if (data.trim().length > 0) { 159 | console.log(this.outFlag.electron + `${chunk.toString()}`); 160 | } 161 | }); 162 | console.log(this.outFlag.control + `Start time: ${Date.now() - this.oldTime}ms`); 163 | } 164 | mainHMR() { 165 | import_chokidar.watch(this.main).on("change", () => { 166 | console.log(this.outFlag.control + `entry file change.restart at ${new Date()}`); 167 | this.isReStart = true; 168 | this.electron.kill(); 169 | this.oldTime = Date.now(); 170 | }); 171 | } 172 | async run() { 173 | this.oldTime = Date.now(); 174 | await this.runRender(); 175 | process.env.CVE_PORT = this.port + ""; 176 | await this.runElectron(); 177 | this.mainHMR(); 178 | } 179 | }; 180 | 181 | // project-script/build.ts 182 | var import_vite3 = __toModule(require("vite")); 183 | var import_fs2 = __toModule(require("fs")); 184 | var import_electron_builder = __toModule(require("electron-builder")); 185 | var import_path2 = __toModule(require("path")); 186 | var chalk3 = __toModule(require("chalk")); 187 | var Build = class extends Base { 188 | async buildRender() { 189 | await import_vite3.build(); 190 | } 191 | getMIRROR() { 192 | let ELECTRON_MIRROR; 193 | let npmrc = import_path2.join(this.root, ".npmrc"); 194 | if (import_fs2.existsSync(npmrc)) { 195 | ELECTRON_MIRROR = import_fs2.readFileSync(npmrc).toString(); 196 | let _t = ELECTRON_MIRROR.split(/[(\r\n)\r\n]+/); 197 | for (let i = 0; i < _t.length; i++) { 198 | let key = _t[i].split("="); 199 | if (key[0] == "ELECTRON_MIRROR") { 200 | ELECTRON_MIRROR = key[1]; 201 | break; 202 | } 203 | } 204 | } 205 | return ELECTRON_MIRROR; 206 | } 207 | async buildProject() { 208 | import_fs2.mkdirSync(import_path2.join(this.outDir, "node_modules")); 209 | let MIRROR = this.getMIRROR(); 210 | await import_electron_builder.build({ 211 | projectDir: this.outDir, 212 | config: { 213 | directories: { 214 | output: import_path2.join(this.root, "release") 215 | }, 216 | extends: null, 217 | electronDownload: { 218 | mirror: MIRROR 219 | } 220 | } 221 | }); 222 | } 223 | mkPkgJson() { 224 | let pkg; 225 | try { 226 | pkg = require(this.pkg); 227 | } catch (error) { 228 | console.error(error); 229 | process.exit(); 230 | } 231 | if (!pkg.main) { 232 | pkg.main = "__entry.js"; 233 | } 234 | let electronVersion = pkg.devDependencies.electron.replace("^", ""); 235 | delete pkg.scripts; 236 | delete pkg.dependencies; 237 | delete pkg.devDependencies; 238 | pkg.devDependencies = {electron: electronVersion}; 239 | import_fs2.writeFileSync(import_path2.join(this.outDir, "package.json"), JSON.stringify(pkg, null, 2)); 240 | return pkg; 241 | } 242 | async run() { 243 | await this.init().catch((e) => { 244 | console.error(chalk3.red`[contorlError] init failed:\n${e}`); 245 | }); 246 | await this.buildRender(); 247 | this.buildMain(); 248 | this.mkPkgJson(); 249 | await this.buildProject().catch((e) => { 250 | console.error(chalk3.red`[electronBuilderError] \n${e}`); 251 | process.exit(); 252 | }); 253 | console.log(this.outFlag.control + ` The packaged application is complete`); 254 | } 255 | }; 256 | 257 | // project-script/index.ts 258 | var method = process.argv.slice(2)[0]; 259 | async function init() { 260 | switch (method) { 261 | case "build": 262 | await new Build().run(); 263 | break; 264 | case "dev": 265 | await new Dev().run(); 266 | break; 267 | default: 268 | console.log(`The argument should be dev or build`); 269 | } 270 | } 271 | init().catch((e) => { 272 | console.log(e); 273 | }); 274 | -------------------------------------------------------------------------------- /template-ts/src/main/entry.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow } from "electron"; 2 | import createProtocol from "./protocol"; 3 | const isDevelopment = process.env.CVE_ENV === 'development' 4 | 5 | app.on('ready', () => { 6 | let win = new BrowserWindow({ 7 | show: false, 8 | webPreferences: { 9 | nodeIntegration: true 10 | } 11 | }) 12 | win.on('ready-to-show', () => { win.show() }) 13 | if (isDevelopment) { 14 | win.loadURL(`http://localhost:${process.env.CVE_PORT}`) 15 | } else { 16 | createProtocol('app') 17 | win.loadURL('app://./index.html') 18 | } 19 | win.webContents.openDevTools() 20 | }) 21 | -------------------------------------------------------------------------------- /template-ts/src/main/protocol.ts: -------------------------------------------------------------------------------- 1 | import { protocol } from "electron"; 2 | import { join, extname } from "path"; 3 | import { readFile } from "fs"; 4 | import { URL } from "url"; 5 | protocol.registerSchemesAsPrivileged([ 6 | { scheme: "app", privileges: { secure: true, standard: true } }, 7 | ]); 8 | export default (scheme: string) => { 9 | protocol.registerBufferProtocol(scheme, (req, res) => { 10 | let pathName = new URL(req.url).pathname; 11 | pathName = decodeURI(pathName); 12 | readFile(join(__dirname, pathName), (e, data) => { 13 | if (e) { 14 | console.error(`Failed to read ${pathName} on ${scheme} protocol`); 15 | } 16 | const extension = extname(pathName).toLowerCase(); 17 | let mimeType = ""; 18 | switch (extension) { 19 | case ".js": 20 | mimeType = "text/javascript"; 21 | break; 22 | case ".html": 23 | mimeType = "text/html"; 24 | break; 25 | case ".css": 26 | mimeType = "text/css"; 27 | break; 28 | case ".svg": 29 | case ".svgz": 30 | mimeType = "image/svg+xml"; 31 | break; 32 | case ".json": 33 | mimeType = "application/json"; 34 | break; 35 | case ".wasm": 36 | mimeType = "application/wasm"; 37 | } 38 | res({ mimeType, data }); 39 | }); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src"], 3 | "compilerOptions": { 4 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 5 | 6 | /* Basic Options */ 7 | // "incremental": true, /* Enable incremental compilation */ 8 | "target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 9 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 10 | // "lib": [], /* Specify library files to be included in the compilation. */ 11 | // "allowJs": true, /* Allow javascript files to be compiled. */ 12 | // "checkJs": true, /* Report errors in .js files. */ 13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 14 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 15 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 16 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 17 | // "outFile": "./", /* Concatenate and emit output to single file. */ 18 | "outDir": "./dist", /* Redirect output structure to the directory. */ 19 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 20 | // "composite": true, /* Enable project compilation */ 21 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 22 | // "removeComments": true, /* Do not emit comments to output. */ 23 | // "noEmit": true, /* Do not emit outputs. */ 24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 27 | 28 | /* Strict Type-Checking Options */ 29 | "strict": true, /* Enable all strict type-checking options. */ 30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 31 | // "strictNullChecks": true, /* Enable strict null checks. */ 32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 33 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 37 | 38 | /* Additional Checks */ 39 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 40 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 43 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 44 | 45 | /* Module Resolution Options */ 46 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 47 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 50 | // "typeRoots": [], /* List of folders to include type definitions from. */ 51 | // "types": [], /* Type declaration files to be included in compilation. */ 52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 53 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 56 | 57 | /* Source Map Options */ 58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 62 | 63 | /* Experimental Options */ 64 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 65 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 66 | 67 | /* Advanced Options */ 68 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 69 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/minimist@^1.2.1": 6 | version "1.2.1" 7 | resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256" 8 | integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg== 9 | 10 | "@types/node@^14.14.25": 11 | version "14.14.28" 12 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.28.tgz#cade4b64f8438f588951a6b35843ce536853f25b" 13 | integrity sha512-lg55ArB+ZiHHbBBttLpzD07akz0QPrZgUODNakeC09i62dnrywr9mFErHuaPlB6I7z+sEbK+IYmplahvplCj2g== 14 | 15 | "@vitejs/create-app@^2.0.0": 16 | version "2.0.0" 17 | resolved "https://registry.yarnpkg.com/@vitejs/create-app/-/create-app-2.0.0.tgz#3935079d4b7d04c39115d6019faae8e6f5df66d7" 18 | integrity sha512-ULTNyMjRROo414g53UqGR2L8jFCYbo1PqOeCGUy0MGpiD/y2bct68+yd9Y4KnhIcr2OFF7p5LSEHgKpjiQCWlg== 19 | dependencies: 20 | enquirer "^2.3.6" 21 | kolorist "^1.2.9" 22 | minimist "^1.2.5" 23 | 24 | ansi-colors@^4.1.1: 25 | version "4.1.1" 26 | resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" 27 | integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== 28 | 29 | enquirer@^2.3.6: 30 | version "2.3.6" 31 | resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" 32 | integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== 33 | dependencies: 34 | ansi-colors "^4.1.1" 35 | 36 | esbuild@^0.8.46: 37 | version "0.8.46" 38 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.8.46.tgz#8fc7230ce3019b12e2553399f0c03875a729c26b" 39 | integrity sha512-xck9sXNCNmjDHCCfxTCyhKTiFuEBweh+IDAhMLOJI990v1Fzii6MyIkT1LbkvjgoVgPX2SK1kpi5eZVGNrl8yg== 40 | 41 | kolorist@^1.2.9: 42 | version "1.2.10" 43 | resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.2.10.tgz#0f97a3f3a1dd75e980765c7cec8a1de258e580c6" 44 | integrity sha512-S3QtGjCHyINclP4LSClgHw4gi/NxTFcSorqD9SWfrREHKtMyGfi6pyDCTbpQaqyZrMAjB4Exde8eco6kejkqQg== 45 | 46 | minimist@^1.2.5: 47 | version "1.2.5" 48 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 49 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 50 | 51 | typescript@^4.1.5: 52 | version "4.1.5" 53 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.5.tgz#123a3b214aaff3be32926f0d8f1f6e704eb89a72" 54 | integrity sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA== 55 | --------------------------------------------------------------------------------