diff --git a/CHANGELOG.md b/CHANGELOG.md index 926bfb6..a556b33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,43 @@ +## 0.4.0 (2023-09-06) + +- 38af79d chore: test v0.4.0 +- f5dafdf chore: update template +- 7114cac chore: site use `https://electron-vite.github.io` +- 394686c refactor: use `vite-plugin-electron` simple API + +#### Main Changed + +**0.4.0** use the simple API of `vite-plugin-electron` + +```ts +import electron from 'vite-plugin-electron/simple' + +electron({ + main: { + entry: 'electron/main.ts', + }, + preload: { + input: __dirname + '/electron/preload.ts', + }, + renderer: {}, +}) +``` + +**0.3.0** + +```ts +import electron from 'vite-plugin-electron' + +electron([ + { + entry: 'electron/main.ts', + }, + { + entry: 'electron/preload.ts', + }, +]) +``` + ## 0.3.0 (2023-05-27) 42dc950 refactor: use Vite instead unbuild diff --git a/__tests__/cli.spec.ts b/__tests__/cli.spec.ts index 18dd2e5..f8bbf4a 100644 --- a/__tests__/cli.spec.ts +++ b/__tests__/cli.spec.ts @@ -1,13 +1,13 @@ -import { join } from 'node:path' +import fs from 'node:fs' +import path from 'node:path' import type { ExecaSyncReturnValue, SyncOptions } from 'execa' import { execaCommandSync } from 'execa' -import fs from 'fs-extra' import { afterEach, beforeAll, expect, test } from 'vitest' -const CLI_PATH = join(__dirname, '..') +const CLI_PATH = path.join(__dirname, '..') const projectName = 'electron-vite-app' -const genPath = join(__dirname, projectName) +const generatePath = path.join(__dirname, projectName) const run = ( args: string[], @@ -18,15 +18,15 @@ const run = ( const createNonEmptyDir = () => { // Create the temporary directory - fs.mkdirpSync(genPath) + fs.mkdirSync(generatePath, { recursive: true }) // Create a package.json file - const pkgJson = join(genPath, 'package.json') + const pkgJson = path.join(generatePath, 'package.json') fs.writeFileSync(pkgJson, '{ "foo": "bar" }') } -beforeAll(() => fs.remove(genPath)) -afterEach(() => fs.remove(genPath)) +beforeAll(() => fs.rmSync(generatePath, { recursive: true, force: true })) +afterEach(() => fs.rmSync(generatePath, { recursive: true, force: true })) test('prompts for the project name if none supplied', () => { const { stdout } = run([]) @@ -34,8 +34,8 @@ test('prompts for the project name if none supplied', () => { }) test('prompts for the framework if none supplied when target dir is current directory', () => { - fs.mkdirpSync(genPath) - const { stdout } = run(['.'], { cwd: genPath }) + fs.mkdirSync(generatePath, { recursive: true }) + const { stdout } = run(['.'], { cwd: generatePath }) expect(stdout).toContain('Project template:') }) @@ -52,6 +52,6 @@ test('asks to overwrite non-empty target directory', () => { test('asks to overwrite non-empty current directory', () => { createNonEmptyDir() - const { stdout } = run(['.'], { cwd: genPath }) + const { stdout } = run(['.'], { cwd: generatePath }) expect(stdout).toContain(`Current directory is not empty.`) }) \ No newline at end of file diff --git a/electron/electron-builder.json5 b/electron/electron-builder.json5 index cc2fca2..3ff2bfe 100644 --- a/electron/electron-builder.json5 +++ b/electron/electron-builder.json5 @@ -5,18 +5,19 @@ "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json", "appId": "YourAppID", "asar": true, + "productName": "YourAppName", "directories": { "output": "release/${version}" }, "files": [ - "dist-electron", - "dist" + "dist", + "dist-electron" ], "mac": { - "artifactName": "${productName}_${version}.${ext}", "target": [ "dmg" - ] + ], + "artifactName": "${productName}-Mac-${version}-Installer.${ext}" }, "win": { "target": [ @@ -27,12 +28,18 @@ ] } ], - "artifactName": "${productName}_${version}.${ext}" + "artifactName": "${productName}-Windows-${version}-Setup.${ext}" }, "nsis": { "oneClick": false, "perMachine": false, "allowToChangeInstallationDirectory": true, "deleteAppDataOnUninstall": false + }, + "linux": { + "target": [ + "AppImage" + ], + "artifactName": "${productName}-Linux-${version}.${ext}" } } diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index 8b65abb..bf0cd46 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -17,6 +17,11 @@ declare namespace NodeJS { */ DIST: string /** /dist/ or /public/ */ - PUBLIC: string + VITE_PUBLIC: string } } + +// Used in Renderer process, expose in `preload.ts` +interface Window { + ipcRenderer: import('electron').IpcRenderer +} diff --git a/electron/main.ts b/electron/main.ts index 201a676..b3b388b 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -11,7 +11,7 @@ import path from 'node:path' // │ │ └── preload.js // │ process.env.DIST = path.join(__dirname, '../dist') -process.env.PUBLIC = app.isPackaged ? process.env.DIST : path.join(process.env.DIST, '../public') +process.env.VITE_PUBLIC = app.isPackaged ? process.env.DIST : path.join(process.env.DIST, '../public') let win: BrowserWindow | null @@ -20,7 +20,7 @@ const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL'] function createWindow() { win = new BrowserWindow({ - icon: path.join(process.env.PUBLIC, 'electron-vite.svg'), + icon: path.join(process.env.VITE_PUBLIC, 'electron-vite.svg'), webPreferences: { preload: path.join(__dirname, 'preload.js'), }, @@ -39,9 +39,22 @@ function createWindow() { } } +// Quit when all windows are closed, except on macOS. There, it's common +// for applications and their menu bar to stay active until the user quits +// explicitly with Cmd + Q. app.on('window-all-closed', () => { - win = null - if (process.platform !== 'darwin') app.quit() + if (process.platform !== 'darwin') { + app.quit() + win = null + } +}) + +app.on('activate', () => { + // On OS X it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (BrowserWindow.getAllWindows().length === 0) { + createWindow() + } }) app.whenReady().then(createWindow) diff --git a/electron/package.json b/electron/package.json index 9bdca18..5305032 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,8 +1,8 @@ { "devDependencies": { - "electron": "^24.4.0", - "electron-builder": "^23.6.0", - "vite-plugin-electron": "^0.11.2", + "electron": "^26.1.0", + "electron-builder": "^24.6.4", + "vite-plugin-electron": "^0.14.0", "vite-plugin-electron-renderer": "^0.14.5" } } \ No newline at end of file diff --git a/electron/preload.ts b/electron/preload.ts index 4a771f8..ec4ff00 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -1,3 +1,28 @@ +import { contextBridge, ipcRenderer } from 'electron' + +// --------- Expose some API to the Renderer process --------- +contextBridge.exposeInMainWorld('ipcRenderer', withPrototype(ipcRenderer)) + +// `exposeInMainWorld` can't detect attributes and methods of `prototype`, manually patching it. +function withPrototype(obj: Record) { + const protos = Object.getPrototypeOf(obj) + + for (const [key, value] of Object.entries(protos)) { + if (Object.prototype.hasOwnProperty.call(obj, key)) continue + + if (typeof value === 'function') { + // Some native APIs, like `NodeJS.EventEmitter['on']`, don't work in the Renderer process. Wrapping them into a function. + obj[key] = function (...args: any) { + return value.call(obj, ...args) + } + } else { + obj[key] = value + } + } + return obj +} + +// --------- Preload scripts loading --------- function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) { return new Promise(resolve => { if (condition.includes(document.readyState)) { diff --git a/package.json b/package.json index 5e34863..5bd5cb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "create-electron-vite", - "version": "0.3.0", + "version": "0.4.0", "type": "module", "description": "Scaffolding Your Electron + Vite Project", "license": "MIT", @@ -34,18 +34,17 @@ "preinstall": "npx only-allow pnpm", "watch": "vite build --watch", "build": "vite build", - "prepublishOnly": "npm run build", + "prepublishOnly": "npm run build && npm run test", + "lint": "eslint .", "test": "vitest run" }, "dependencies": { "prompts": "^2.4.2" }, "devDependencies": { - "@types/fs-extra": "^11.0.1", "@types/node": "^18.11.18", "@types/prompts": "^2.4.2", "execa": "^7.1.1", - "fs-extra": "^11.1.1", "typescript": "^4.9.4", "vite": "^4.3.9", "vitest": "^0.29.3" @@ -53,5 +52,5 @@ "engines": { "node": "^14.18.0 || >=16.0.0" }, - "packageManager": "pnpm@8.6.0" + "packageManager": "pnpm@8.0.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1060309..48b2685 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,9 +6,6 @@ dependencies: version: 2.4.2 devDependencies: - '@types/fs-extra': - specifier: ^11.0.1 - version: 11.0.1 '@types/node': specifier: ^18.11.18 version: 18.11.18 @@ -18,9 +15,6 @@ devDependencies: execa: specifier: ^7.1.1 version: 7.1.1 - fs-extra: - specifier: ^11.1.1 - version: 11.1.1 typescript: specifier: ^4.9.4 version: 4.9.4 @@ -241,19 +235,6 @@ packages: resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==} dev: true - /@types/fs-extra@11.0.1: - resolution: {integrity: sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==} - dependencies: - '@types/jsonfile': 6.1.1 - '@types/node': 18.11.18 - dev: true - - /@types/jsonfile@6.1.1: - resolution: {integrity: sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==} - dependencies: - '@types/node': 18.11.18 - dev: true - /@types/node@18.11.18: resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} dev: true @@ -453,15 +434,6 @@ packages: strip-final-newline: 3.0.0 dev: true - /fs-extra@11.1.1: - resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} - engines: {node: '>=14.14'} - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.0 - dev: true - /fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -479,10 +451,6 @@ packages: engines: {node: '>=10'} dev: true - /graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: true - /human-signals@4.3.1: resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} engines: {node: '>=14.18.0'} @@ -506,14 +474,6 @@ packages: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} dev: true - /jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - dependencies: - universalify: 2.0.0 - optionalDependencies: - graceful-fs: 4.2.11 - dev: true - /kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -752,11 +712,6 @@ packages: resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==} dev: true - /universalify@2.0.0: - resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} - engines: {node: '>= 10.0.0'} - dev: true - /vite-node@0.29.3(@types/node@18.11.18): resolution: {integrity: sha512-QYzYSA4Yt2IiduEjYbccfZQfxKp+T1Do8/HEpSX/G5WIECTFKJADwLs9c94aQH4o0A+UtCKU61lj1m5KvbxxQA==} engines: {node: '>=v14.16.0'} diff --git a/src/index.ts b/src/index.ts index 6290eac..f46e464 100644 --- a/src/index.ts +++ b/src/index.ts @@ -272,46 +272,56 @@ function setupElectron(root: string, framework: Framework) { }) // main.ts - const snippets = `postMessage({ payload: 'removeLoading' }, '*')` + const snippets = (indent = 0) => ` +// Remove Preload scripts loading +postMessage({ payload: 'removeLoading' }, '*') + +// Use contextBridge +window.ipcRenderer.on('main-process-message', (_event, message) => { + console.log(message) +}) +`.trim() + .split('\n') + .map(line => line ? ' '.repeat(indent) + line : line) + .join('\n') if (framework === 'vue') { editFile(path.join(root, 'src/main.ts'), content => - content.replace(`mount('#app')`, `mount('#app').$nextTick(() => ${snippets})`) + content.replace(`mount('#app')`, `mount('#app').$nextTick(() => {\n${snippets(2)}\n})`) ) } else if (framework === 'react') { - editFile(path.join(root, 'src/main.tsx'), content => `${content}\n${snippets}\n`) + editFile(path.join(root, 'src/main.tsx'), content => `${content}\n${snippets()}\n`) } else if (framework === 'vanilla') { - editFile(path.join(root, 'src/main.ts'), content => `${content}\n${snippets}\n`) + editFile(path.join(root, 'src/main.ts'), content => `${content}\n${snippets()}\n`) } // vite.config.ts - const electronPlugin = `electron([ - { - // Main-Process entry file of the Electron App. + const electronPlugin = `electron({ + main: { + // Shortcut of \`build.lib.entry\`. entry: 'electron/main.ts', }, - { - entry: 'electron/preload.ts', - onstart(options) { - // Notify the Renderer-Process to reload the page when the Preload-Scripts build is complete, - // instead of restarting the entire Electron App. - options.reload() - }, + preload: { + // Shortcut of \`build.rollupOptions.input\`. + // Preload scripts may contain Web assets, so use the \`build.rollupOptions.input\` instead \`build.lib.entry\`. + input: path.join(__dirname, 'electron/preload.ts'), }, - ])` + // Ployfill the Electron and Node.js built-in modules for Renderer process. + // See 👉 https://github.com/electron-vite/vite-plugin-electron-renderer + renderer: {}, + })` if (framework === 'vue' || framework === 'react') { editFile(path.join(root, 'vite.config.ts'), content => content .split('\n') .map(line => line.includes("import { defineConfig } from 'vite'") ? `${line} -import electron from 'vite-plugin-electron' -import renderer from 'vite-plugin-electron-renderer'` +import path from 'node:path' +import electron from 'vite-plugin-electron/simple'` : line) .map(line => line.trimStart().startsWith('plugins') ? ` plugins: [ ${framework}(), ${electronPlugin}, - renderer(), ],` : line) .join('\n') @@ -321,14 +331,13 @@ import renderer from 'vite-plugin-electron-renderer'` path.join(root, 'vite.config.ts'), ` import { defineConfig } from 'vite' -import electron from 'vite-plugin-electron' -import renderer from 'vite-plugin-electron-renderer' +import path from 'node:path' +import electron from 'vite-plugin-electron/simple' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ ${electronPlugin}, - renderer(), ], }) `.trimStart() @@ -351,13 +360,20 @@ export default defineConfig({ .join('\n') ) - // electron-vite.svg + // site 👉 https://electron-vite.github.io + // logo 👉 electron-vite.svg if (framework === 'vue') { - editFile(path.join(root, 'src/App.vue'), content => content.replace('/vite.svg', '/electron-vite.svg')) + editFile(path.join(root, 'src/App.vue'), content => content + .replace('https://vitejs.dev', 'https://electron-vite.github.io') + .replace('/vite.svg', '/electron-vite.svg')) } else if (framework === 'react') { - editFile(path.join(root, 'src/App.tsx'), content => content.replace('/vite.svg', '/electron-vite.animate.svg')) + editFile(path.join(root, 'src/App.tsx'), content => content + .replace('https://vitejs.dev', 'https://electron-vite.github.io') + .replace('/vite.svg', '/electron-vite.animate.svg')) } else if (framework === 'vanilla') { - editFile(path.join(root, 'src/main.ts'), content => content.replace('/vite.svg', '/electron-vite.svg')) + editFile(path.join(root, 'src/main.ts'), content => content + .replace('https://vitejs.dev', 'https://electron-vite.github.io') + .replace('/vite.svg', '/electron-vite.svg')) } } diff --git a/template-react-ts/.eslintrc.cjs b/template-react-ts/.eslintrc.cjs index 4020bcb..d6c9537 100644 --- a/template-react-ts/.eslintrc.cjs +++ b/template-react-ts/.eslintrc.cjs @@ -1,14 +1,18 @@ module.exports = { + root: true, env: { browser: true, es2020: true }, extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', ], + ignorePatterns: ['dist', '.eslintrc.cjs'], parser: '@typescript-eslint/parser', - parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, plugins: ['react-refresh'], rules: { - 'react-refresh/only-export-components': 'warn', + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], }, } diff --git a/template-react-ts/index.html b/template-react-ts/index.html index e0d1c84..e4b78ea 100644 --- a/template-react-ts/index.html +++ b/template-react-ts/index.html @@ -1,4 +1,4 @@ - + diff --git a/template-react-ts/package.json b/template-react-ts/package.json index 63bd229..978382a 100644 --- a/template-react-ts/package.json +++ b/template-react-ts/package.json @@ -14,15 +14,15 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@types/react": "^18.0.37", - "@types/react-dom": "^18.0.11", - "@typescript-eslint/eslint-plugin": "^5.59.0", - "@typescript-eslint/parser": "^5.59.0", - "@vitejs/plugin-react": "^4.0.0", - "eslint": "^8.38.0", + "@types/react": "^18.2.21", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.6.0", + "@typescript-eslint/parser": "^6.6.0", + "@vitejs/plugin-react": "^4.0.4", + "eslint": "^8.48.0", "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.3.4", - "typescript": "^5.0.2", - "vite": "^4.3.2" + "eslint-plugin-react-refresh": "^0.4.3", + "typescript": "^5.2.2", + "vite": "^4.4.9" } } diff --git a/template-react-ts/src/main.tsx b/template-react-ts/src/main.tsx index 91c03f3..3d7150d 100644 --- a/template-react-ts/src/main.tsx +++ b/template-react-ts/src/main.tsx @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client' import App from './App.tsx' import './index.css' -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( +ReactDOM.createRoot(document.getElementById('root')!).render( , diff --git a/template-vanilla-ts/index.html b/template-vanilla-ts/index.html index f86e483..44a9335 100644 --- a/template-vanilla-ts/index.html +++ b/template-vanilla-ts/index.html @@ -1,4 +1,4 @@ - + diff --git a/template-vanilla-ts/package.json b/template-vanilla-ts/package.json index 7c063e0..75f6764 100644 --- a/template-vanilla-ts/package.json +++ b/template-vanilla-ts/package.json @@ -9,7 +9,7 @@ "preview": "vite preview" }, "devDependencies": { - "typescript": "^5.0.2", - "vite": "^4.3.2" + "typescript": "^5.2.2", + "vite": "^4.4.9" } } diff --git a/template-vue-ts/index.html b/template-vue-ts/index.html index 143557b..dde16aa 100644 --- a/template-vue-ts/index.html +++ b/template-vue-ts/index.html @@ -1,4 +1,4 @@ - + diff --git a/template-vue-ts/package.json b/template-vue-ts/package.json index 6dfe106..b795f14 100644 --- a/template-vue-ts/package.json +++ b/template-vue-ts/package.json @@ -9,12 +9,12 @@ "preview": "vite preview" }, "dependencies": { - "vue": "^3.2.47" + "vue": "^3.3.4" }, "devDependencies": { - "@vitejs/plugin-vue": "^4.1.0", - "typescript": "^5.0.2", - "vite": "^4.3.2", - "vue-tsc": "^1.4.2" + "@vitejs/plugin-vue": "^4.3.4", + "typescript": "^5.2.2", + "vite": "^4.4.9", + "vue-tsc": "^1.8.8" } } diff --git a/template-vue-ts/tsconfig.json b/template-vue-ts/tsconfig.json index f82888f..9e03e60 100644 --- a/template-vue-ts/tsconfig.json +++ b/template-vue-ts/tsconfig.json @@ -20,6 +20,6 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }] }