-
-
Notifications
You must be signed in to change notification settings - Fork 631
feat(template): add vite-typescript template #3178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
82309e2
e9cfca2
48dfa56
7166887
f4b23b9
488aa0a
a7198dd
e2e74a6
539518a
4f1b7fd
06957e9
0f4ec09
d94fc8b
96e1924
a61c588
d8d8ef1
762d6e8
70f2245
a5d04fd
8e17895
0b1ffd2
4f1aa6e
3c0de1f
a1c1688
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| tmpl |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| { | ||
| "name": "@electron-forge/template-vite-typescript", | ||
| "version": "6.3.0", | ||
| "description": "Vite-TypeScript template for Electron Forge, gets you started with Vite really quickly", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/electron/forge", | ||
| "directory": "packages/template/vite-typescript" | ||
| }, | ||
| "author": "caoxiemeihao", | ||
| "license": "MIT", | ||
| "main": "dist/ViteTypeScriptTemplate.js", | ||
| "typings": "dist/ViteTypeScriptTemplate.d.ts", | ||
| "scripts": { | ||
| "test": "mocha --config ../../../.mocharc.js test/**/*_spec_slow.ts" | ||
| }, | ||
| "engines": { | ||
| "node": "^14.18.0 || >=16.0.0" | ||
| }, | ||
| "dependencies": { | ||
| "@electron-forge/shared-types": "6.3.0", | ||
| "@electron-forge/template-base": "6.3.0", | ||
| "fs-extra": "^10.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@electron-forge/core-utils": "6.3.0", | ||
| "@electron-forge/test-utils": "6.3.0", | ||
| "chai": "^4.3.3", | ||
| "fast-glob": "^3.2.7" | ||
| }, | ||
| "files": [ | ||
| "dist", | ||
| "src", | ||
| "tmpl" | ||
| ] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import path from 'path'; | ||
|
|
||
| import { ForgeListrTaskDefinition, InitTemplateOptions } from '@electron-forge/shared-types'; | ||
| import { BaseTemplate } from '@electron-forge/template-base'; | ||
| import fs from 'fs-extra'; | ||
|
|
||
| class ViteTypeScriptTemplate extends BaseTemplate { | ||
| public templateDir = path.resolve(__dirname, '..', 'tmpl'); | ||
|
|
||
| public async initializeTemplate(directory: string, options: InitTemplateOptions): Promise<ForgeListrTaskDefinition[]> { | ||
| const superTasks = await super.initializeTemplate(directory, options); | ||
| return [ | ||
| ...superTasks, | ||
| { | ||
| title: 'Setting up Forge configuration', | ||
| task: async () => { | ||
| await this.copyTemplateFile(directory, 'forge.config.ts'); | ||
| await fs.remove(path.resolve(directory, 'forge.config.js')); | ||
| }, | ||
| }, | ||
| { | ||
| title: 'Preparing TypeScript files and configuration', | ||
| task: async () => { | ||
| const filePath = (fileName: string) => path.join(directory, 'src', fileName); | ||
|
|
||
| // Copy Vite files | ||
| await this.copyTemplateFile(directory, 'vite.main.config.ts'); | ||
| await this.copyTemplateFile(directory, 'vite.renderer.config.ts'); | ||
| await this.copyTemplateFile(directory, 'vite.preload.config.ts'); | ||
|
|
||
| // Copy tsconfig with a small set of presets | ||
| await this.copyTemplateFile(directory, 'tsconfig.json'); | ||
|
|
||
| // Copy eslint config with recommended settings | ||
| await this.copyTemplateFile(directory, '.eslintrc.json'); | ||
|
|
||
| // Remove index.js and replace with main.ts | ||
| await fs.remove(filePath('index.js')); | ||
| await this.copyTemplateFile(path.join(directory, 'src'), 'main.ts'); | ||
|
|
||
| await this.copyTemplateFile(path.join(directory, 'src'), 'renderer.ts'); | ||
| await this.copyTemplateFile(path.join(directory, 'src'), 'types.d.ts'); | ||
|
|
||
| // Remove preload.js and replace with preload.ts | ||
| await fs.remove(filePath('preload.js')); | ||
| await this.copyTemplateFile(path.join(directory, 'src'), 'preload.ts'); | ||
|
|
||
| // TODO: Compatible with any path entry. | ||
| // Vite uses index.html under the root path as the entry point. | ||
| await fs.move(filePath('index.html'), path.join(directory, 'index.html')); | ||
| await this.updateFileByLine(path.join(directory, 'index.html'), (line) => { | ||
| if (line.includes('link rel="stylesheet"')) return ''; | ||
| if (line.includes('</body>')) return ' <script type="module" src="/src/renderer.ts"></script>\n </body>'; | ||
| return line; | ||
| }); | ||
|
|
||
| // update package.json | ||
| const packageJSONPath = path.resolve(directory, 'package.json'); | ||
| const packageJSON = await fs.readJson(packageJSONPath); | ||
| packageJSON.main = '.vite/build/main.js'; | ||
| // Configure scripts for TS template | ||
| packageJSON.scripts.lint = 'eslint --ext .ts,.tsx .'; | ||
| await fs.writeJson(packageJSONPath, packageJSON, { | ||
| spaces: 2, | ||
| }); | ||
| }, | ||
| }, | ||
| ]; | ||
| } | ||
| } | ||
|
|
||
| export default new ViteTypeScriptTemplate(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| import cp from 'child_process'; | ||
| import path from 'path'; | ||
|
|
||
| import { yarnOrNpmSpawn } from '@electron-forge/core-utils'; | ||
| import * as testUtils from '@electron-forge/test-utils'; | ||
| import { expect } from 'chai'; | ||
| import glob from 'fast-glob'; | ||
| import fs from 'fs-extra'; | ||
|
|
||
| import { api } from '../../../api/core'; | ||
| import { initLink } from '../../../api/core/src/api/init-scripts/init-link'; | ||
|
|
||
| describe('ViteTypeScriptTemplate', () => { | ||
| let dir: string; | ||
|
|
||
| before(async () => { | ||
| await yarnOrNpmSpawn(['link:prepare']); | ||
| dir = await testUtils.ensureTestDirIsNonexistent(); | ||
| }); | ||
|
|
||
| after(async () => { | ||
| await yarnOrNpmSpawn(['link:remove']); | ||
| await killWindowsEsbuildExe(); | ||
| await fs.remove(dir); | ||
| }); | ||
|
|
||
| describe('template files are copied to project', () => { | ||
| it('should succeed in initializing the typescript template', async () => { | ||
| await api.init({ | ||
| dir, | ||
| template: path.resolve(__dirname, '..', 'src', 'ViteTypeScriptTemplate'), | ||
| interactive: false, | ||
| }); | ||
| }); | ||
|
|
||
| const expectedFiles = [ | ||
| 'tsconfig.json', | ||
| '.eslintrc.json', | ||
| 'forge.config.ts', | ||
| 'vite.main.config.ts', | ||
| 'vite.renderer.config.ts', | ||
| 'vite.preload.config.ts', | ||
| path.join('src', 'main.ts'), | ||
| path.join('src', 'renderer.ts'), | ||
| path.join('src', 'preload.ts'), | ||
| path.join('src', 'types.d.ts'), | ||
| ]; | ||
| for (const filename of expectedFiles) { | ||
| it(`${filename} should exist`, async () => { | ||
| await testUtils.expectProjectPathExists(dir, filename, 'file'); | ||
| }); | ||
| } | ||
|
|
||
| it('should ensure js source files from base template are removed', async () => { | ||
| const jsFiles = await glob(path.join(dir, 'src', '**', '*.js')); | ||
| expect(jsFiles.length).to.equal(0, `The following unexpected js files were found in the src/ folder: ${JSON.stringify(jsFiles)}`); | ||
| }); | ||
| }); | ||
|
|
||
| describe('lint', () => { | ||
| it('should initially pass the linting process', async () => { | ||
| delete process.env.TS_NODE_PROJECT; | ||
| await testUtils.expectLintToPass(dir); | ||
| }); | ||
| }); | ||
|
|
||
| describe('package', () => { | ||
| let cwd: string; | ||
|
|
||
| before(async () => { | ||
| delete process.env.TS_NODE_PROJECT; | ||
| // Vite resolves plugins via cwd | ||
| cwd = process.cwd(); | ||
| process.chdir(dir); | ||
| // We need the version of vite to match exactly during development due to a quirk in | ||
| // typescript type-resolution. In prod no one has to worry about things like this | ||
| const pj = await fs.readJson(path.resolve(dir, 'package.json')); | ||
| pj.resolutions = { | ||
| // eslint-disable-next-line @typescript-eslint/no-var-requires | ||
| vite: `${require('../../../../node_modules/vite/package.json').version}`, | ||
| }; | ||
| await fs.writeJson(path.resolve(dir, 'package.json'), pj); | ||
| await yarnOrNpmSpawn(['install'], { | ||
| cwd: dir, | ||
| }); | ||
|
|
||
| // Installing deps removes symlinks that were added at the start of this | ||
| // spec via `api.init`. So we should re-link local forge dependencies | ||
| // again. | ||
| await initLink(dir); | ||
| }); | ||
|
|
||
| after(() => { | ||
| process.chdir(cwd); | ||
| }); | ||
|
|
||
| it('should pass', async () => { | ||
| await api.package({ | ||
| dir, | ||
| interactive: false, | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| /** | ||
| * TODO: resolve `esbuild` can not exit normally on the Windows platform. | ||
| * @deprecated | ||
| */ | ||
| async function killWindowsEsbuildExe() { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @BlackHole1 Until Vite fix it, we can resolve the problem like this.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks!
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there an open issue tracking this for Vite? |
||
| if (process.platform !== 'win32') { | ||
| return Promise.resolve(); | ||
| } | ||
|
|
||
| return new Promise<void>((resolve, reject) => { | ||
| cp.exec('tasklist', (error, stdout) => { | ||
| if (error) { | ||
| reject(error); | ||
| return; | ||
| } | ||
|
|
||
| const esbuild = stdout | ||
| .toString() | ||
| .split('\n') | ||
| .map((line) => line.split(/\s+/)) | ||
| .find((line) => line.includes('esbuild.exe')); | ||
|
|
||
| if (!esbuild) { | ||
| resolve(); | ||
| return; | ||
| } | ||
|
|
||
| // ['esbuild.exe', '4564', 'Console', '1', '14,400', 'K', ''] | ||
| const [, pid] = esbuild; | ||
| const result = process.kill(+pid, 'SIGINT'); | ||
|
|
||
| if (result) { | ||
| resolve(); | ||
| } else { | ||
| reject(new Error('kill esbuild process failed')); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| { | ||
| "env": { | ||
| "browser": true, | ||
| "es6": true, | ||
| "node": true | ||
| }, | ||
| "extends": [ | ||
| "eslint:recommended", | ||
| "plugin:@typescript-eslint/eslint-recommended", | ||
| "plugin:@typescript-eslint/recommended", | ||
| "plugin:import/recommended", | ||
| "plugin:import/electron", | ||
| "plugin:import/typescript" | ||
| ], | ||
| "parser": "@typescript-eslint/parser" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import type { ForgeConfig } from '@electron-forge/shared-types'; | ||
| import { MakerSquirrel } from '@electron-forge/maker-squirrel'; | ||
| import { MakerZIP } from '@electron-forge/maker-zip'; | ||
| import { MakerDeb } from '@electron-forge/maker-deb'; | ||
| import { MakerRpm } from '@electron-forge/maker-rpm'; | ||
| import { VitePlugin } from '@electron-forge/plugin-vite'; | ||
|
|
||
| const config: ForgeConfig = { | ||
| packagerConfig: {}, | ||
| rebuildConfig: {}, | ||
| makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})], | ||
| plugins: [ | ||
| new VitePlugin({ | ||
| // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc. | ||
| // If you are familiar with Vite configuration, it will look really familiar. | ||
| build: [ | ||
| { | ||
| // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`. | ||
| entry: 'src/main.ts', | ||
| config: 'vite.main.config.ts', | ||
| }, | ||
| { | ||
| entry: 'src/preload.ts', | ||
| config: 'vite.preload.config.ts', | ||
| }, | ||
| ], | ||
| renderer: [ | ||
| { | ||
| name: 'main_window', | ||
| config: 'vite.renderer.config.ts', | ||
| }, | ||
| ], | ||
| }), | ||
| ], | ||
| }; | ||
|
|
||
| export default config; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { app, BrowserWindow } from 'electron'; | ||
| import path from 'path'; | ||
|
|
||
| // Handle creating/removing shortcuts on Windows when installing/uninstalling. | ||
| if (require('electron-squirrel-startup')) { | ||
| app.quit(); | ||
| } | ||
|
|
||
| const createWindow = () => { | ||
| // Create the browser window. | ||
| const mainWindow = new BrowserWindow({ | ||
| width: 800, | ||
| height: 600, | ||
| webPreferences: { | ||
| preload: path.join(__dirname, 'preload.js'), | ||
| }, | ||
| }); | ||
|
|
||
| // and load the index.html of the app. | ||
| if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { | ||
| mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); | ||
| } else { | ||
| mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)); | ||
| } | ||
|
|
||
| // Open the DevTools. | ||
| mainWindow.webContents.openDevTools(); | ||
| }; | ||
|
|
||
| // This method will be called when Electron has finished | ||
| // initialization and is ready to create browser windows. | ||
| // Some APIs can only be used after this event occurs. | ||
| app.on('ready', 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', () => { | ||
| if (process.platform !== 'darwin') { | ||
| app.quit(); | ||
| } | ||
| }); | ||
|
|
||
| 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(); | ||
| } | ||
| }); | ||
|
|
||
| // In this file you can include the rest of your app's specific main process | ||
| // code. You can also put them in separate files and import them here. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "devDependencies": { | ||
| "@electron-forge/plugin-vite": "ELECTRON_FORGE/VERSION", | ||
| "@typescript-eslint/eslint-plugin": "^5.0.0", | ||
| "@typescript-eslint/parser": "^5.0.0", | ||
| "eslint": "^8.0.1", | ||
| "eslint-plugin-import": "^2.25.0", | ||
| "ts-node": "^10.0.0", | ||
| "typescript": "~4.5.4" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| // See the Electron documentation for details on how to use preload scripts: | ||
| // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts |
Uh oh!
There was an error while loading. Please reload this page.