mirror of
https://github.com/alex8088/electron-vite.git
synced 2025-06-24 03:01:45 +08:00
Compare commits
250 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9ec164d33e | ||
|
a9197f5cc9 | ||
|
3d5c9f68a1 | ||
|
1b411d3633 | ||
|
b56d3c2d21 | ||
|
f2eff25268 | ||
|
2d8e513e07 | ||
|
d8063320dc | ||
|
f33c5b2abe | ||
|
e91e70c105 | ||
|
ea144aef19 | ||
|
987c55ee8b | ||
|
8064bd81ff | ||
|
6e8572d9b7 | ||
|
4b47ef0bd4 | ||
|
5a5af050b2 | ||
|
96ae3c5cd9 | ||
|
79ac91dee2 | ||
|
1599d730f6 | ||
|
3c6e08b2f2 | ||
|
dfe6a3e3f8 | ||
|
6c01417909 | ||
|
5ffd49eddc | ||
|
bf1220875f | ||
|
02e0dff9f4 | ||
|
0a02ace008 | ||
|
e638dcae1b | ||
|
3264abc70b | ||
|
19b42225f8 | ||
|
d9aaf24f84 | ||
|
3605aca1e8 | ||
|
73dfee5a4f | ||
|
b3185d7fc5 | ||
|
d79de5abb6 | ||
|
1838bdbf3e | ||
|
ff23f7d44d | ||
|
a48e12a9df | ||
|
1abedce6c2 | ||
|
c2c655367f | ||
|
6170705eae | ||
|
eeaa7de45c | ||
|
f42a9d370f | ||
|
08ff86f2c4 | ||
|
0ce505a12f | ||
|
ad891af811 | ||
|
27ade03acf | ||
|
52fce25787 | ||
|
19489a28c8 | ||
|
7f13ea2a84 | ||
|
932fc556f5 | ||
|
a12646f25e | ||
|
921aa9d4a7 | ||
|
694e134a52 | ||
|
40a1b64639 | ||
|
476ef54498 | ||
|
9b04362e12 | ||
|
a8f5182d25 | ||
|
0c98f33573 | ||
|
db1128089a | ||
|
0b265442a1 | ||
|
7b27e684d6 | ||
|
dbd7b0137b | ||
|
73f2562172 | ||
|
cc0df8a8cc | ||
|
93e84f6bce | ||
|
619a337c6d | ||
|
963f8aba51 | ||
|
082b1331ff | ||
|
b578f61f1c | ||
|
5de87d681b | ||
|
a7b3c65576 | ||
|
4924e36d74 | ||
|
61eb32aa04 | ||
|
c1e1ca0471 | ||
|
cb58ef8649 | ||
|
93763597fe | ||
|
dc87a798ab | ||
|
4f12196e54 | ||
|
b93a1878c1 | ||
|
8bb03f8b24 | ||
|
f951edf492 | ||
|
7c6020e11b | ||
|
4e48537b39 | ||
|
10099eced0 | ||
|
fbc45f696b | ||
|
d30d3765eb | ||
|
5c4c28ca0a | ||
|
5e31794c2d | ||
|
588b39b0b4 | ||
|
ce56ecd4dc | ||
|
5021496f74 | ||
|
8e1f9f6845 | ||
|
7369960b99 | ||
|
ac47dacc1b | ||
|
2360d502a4 | ||
|
01163866fb | ||
|
8b839872f8 | ||
|
670c23b2e1 | ||
|
566f88960e | ||
|
8a774ef76d | ||
|
27acfb7c1d | ||
|
5c440a8a76 | ||
|
ed64873434 | ||
|
254ce0b5b6 | ||
|
0c46c1064b | ||
|
6c590ad65b | ||
|
d8307e8fd4 | ||
|
532ba52714 | ||
|
8914878a53 | ||
|
d7b4ebc54a | ||
|
342a0556cd | ||
|
9d76799072 | ||
|
f0f21db904 | ||
|
11b6f3c39d | ||
|
ed13174829 | ||
|
583c318af1 | ||
|
3507149b6e | ||
|
81117c6809 | ||
|
3567f26f02 | ||
|
9f5ca0d92d | ||
|
8b445b67c5 | ||
|
c43d1832d2 | ||
|
0367f73a95 | ||
|
785b32f2b0 | ||
|
4f796e4c8a | ||
|
3def0ed84b | ||
|
b71bf82f02 | ||
|
587efabac5 | ||
|
bac8c04816 | ||
|
6dff3475b4 | ||
|
c26a9ff71c | ||
|
388b590808 | ||
|
8c924188de | ||
|
6627e81f66 | ||
|
f443c18056 | ||
|
a736af835e | ||
|
3b2ba0ae25 | ||
|
72d37a70ca | ||
|
c0d9ad3fd5 | ||
|
3a69fbf489 | ||
|
6c6db628f6 | ||
|
f33c86383e | ||
|
1cb7d419a2 | ||
|
103beec87b | ||
|
f0c1a431b0 | ||
|
ee4d400fb0 | ||
|
5ad0747f32 | ||
|
f7f19f9649 | ||
|
05e0cc6db1 | ||
|
55be651207 | ||
|
a84ae9e7e0 | ||
|
0d837e4356 | ||
|
8dd87715e7 | ||
|
9c34b6c884 | ||
|
678bfa616d | ||
|
b0fc657db4 | ||
|
5ed5fe9352 | ||
|
c3567edba9 | ||
|
32d78f2f42 | ||
|
bc6448df9b | ||
|
b85cd5ecf2 | ||
|
44af7960a2 | ||
|
b3208d3dae | ||
|
4989595464 | ||
|
127b09ebd8 | ||
|
efd21c0433 | ||
|
9883399bf3 | ||
|
62e8be1c34 | ||
|
336b4292eb | ||
|
f7b4146c56 | ||
|
a5de5b36f5 | ||
|
edd9ccb286 | ||
|
ad1ca5dcd7 | ||
|
95736032f2 | ||
|
3365e87041 | ||
|
b4d1e7466b | ||
|
9d5cd15a3f | ||
|
d086e0d51a | ||
|
6676f60aae | ||
|
09705f7099 | ||
|
16d9932bac | ||
|
ffb2426e75 | ||
|
767aee7464 | ||
|
067c333aeb | ||
|
e72e1ea056 | ||
|
23ae7636cd | ||
|
21e10c023a | ||
|
230d09c2a0 | ||
|
baf538cfe4 | ||
|
41ff7372c2 | ||
|
cf1151ef5f | ||
|
10f5ae64a3 | ||
|
28d069c453 | ||
|
bf0c83835a | ||
|
f81f57efbf | ||
|
1668547dbf | ||
|
97ad5fa726 | ||
|
6bae6ac6b4 | ||
|
39f548a30a | ||
|
caafa1355d | ||
|
97a62b75f2 | ||
|
a62b20359b | ||
|
d0476dd91e | ||
|
229c398235 | ||
|
f09a482c66 | ||
|
f8749646ee | ||
|
811e6a1e1e | ||
|
59e1d93166 | ||
|
c49068258d | ||
|
cf08073f08 | ||
|
969dc30e50 | ||
|
fab097e367 | ||
|
51b9f74457 | ||
|
dd6322f9c8 | ||
|
8556a45cab | ||
|
6e04db0a14 | ||
|
555d4b43a9 | ||
|
19bfb2a829 | ||
|
556fe95f20 | ||
|
da50aa9246 | ||
|
9132b24751 | ||
|
4779bcf911 | ||
|
cd0bbb6cfb | ||
|
c6bade23b5 | ||
|
4e53a28650 | ||
|
5db96e2a36 | ||
|
bf27b464d9 | ||
|
2132eccb28 | ||
|
714798fd56 | ||
|
1bd15486ea | ||
|
006418ab14 | ||
|
ab6d089aa6 | ||
|
b62b8ec0bb | ||
|
ffda7049c7 | ||
|
d24519d71b | ||
|
a5f6f13b9c | ||
|
7a60cce781 | ||
|
5dc1ddc6ad | ||
|
d47984cf4f | ||
|
cc2e71bed3 | ||
|
cec47266bd | ||
|
71dc5fd18b | ||
|
839a1801a3 | ||
|
44b8eee48f | ||
|
04abdda294 | ||
|
e35552b4b6 | ||
|
c568348fc9 | ||
|
c71d5aa661 | ||
|
68f660fbc3 | ||
|
62b21ee994 |
@ -1,2 +0,0 @@
|
|||||||
node_modules
|
|
||||||
dist
|
|
38
.eslintrc.js
38
.eslintrc.js
@ -1,38 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
commonjs: true,
|
|
||||||
es6: true,
|
|
||||||
node: true
|
|
||||||
},
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: 'module',
|
|
||||||
ecmaVersion: 2021
|
|
||||||
},
|
|
||||||
plugins: ['@typescript-eslint'],
|
|
||||||
extends: [
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:@typescript-eslint/eslint-recommended',
|
|
||||||
'plugin:prettier/recommended'
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
'no-empty': ['warn', { allowEmptyCatch: true }],
|
|
||||||
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': 'allow-with-description' }],
|
|
||||||
'@typescript-eslint/explicit-function-return-type': 'error',
|
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
||||||
'@typescript-eslint/no-empty-function': ['error', { allow: ['arrowFunctions'] }],
|
|
||||||
'@typescript-eslint/no-explicit-any': 'error',
|
|
||||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
|
||||||
'@typescript-eslint/no-var-requires': 'off'
|
|
||||||
},
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['*.js'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
github: alex8088
|
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,6 +1,5 @@
|
|||||||
name: "\U0001F41E Bug Report"
|
name: "\U0001F41E Bug Report"
|
||||||
description: Report an issue with electron-vite
|
description: Report an issue with electron-vite
|
||||||
labels: ['bug', 'triage']
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
@ -45,7 +44,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: Read the [Contributing Guidelines](https://github.com/alex8088/electron-vite/blob/master/CONTRIBUTING.md).
|
- label: Read the [Contributing Guidelines](https://github.com/alex8088/electron-vite/blob/master/CONTRIBUTING.md).
|
||||||
required: true
|
required: true
|
||||||
- label: Read the [docs](https://github.com/alex8088/electron-vite#readme).
|
- label: Read the [docs](https://electron-vite.org).
|
||||||
required: true
|
required: true
|
||||||
- label: Check that there isn't [already an issue](https://github.com/alex8088/electron-vite/issues) that reports the same bug to avoid creating a duplicate.
|
- label: Check that there isn't [already an issue](https://github.com/alex8088/electron-vite/issues) that reports the same bug to avoid creating a duplicate.
|
||||||
required: true
|
required: true
|
||||||
|
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1 +1,5 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Questions & Discussions
|
||||||
|
url: https://github.com/alex8088/electron-vite/discussions
|
||||||
|
about: Use GitHub discussions for message-board style questions and discussions.
|
||||||
|
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -40,7 +40,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: Read the [Contributing Guidelines](https://github.com/alex8088/electron-vite/blob/master/CONTRIBUTING.md).
|
- label: Read the [Contributing Guidelines](https://github.com/alex8088/electron-vite/blob/master/CONTRIBUTING.md).
|
||||||
required: true
|
required: true
|
||||||
- label: Read the [docs](https://github.com/alex8088/electron-vite#readme).
|
- label: Read the [docs](https://electron-vite.org).
|
||||||
required: true
|
required: true
|
||||||
- label: Check that there isn't [already an issue](https://github.com/alex8088/electron-vite/issues) that reports the same bug to avoid creating a duplicate.
|
- label: Check that there isn't [already an issue](https://github.com/alex8088/electron-vite/issues) that requests the same feature to avoid creating a duplicate.
|
||||||
required: true
|
required: true
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
|
.DS_Store
|
||||||
|
.eslintcache
|
||||||
*.log*
|
*.log*
|
||||||
|
11
.vscode/settings.json
vendored
Normal file
11
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
}
|
||||||
|
}
|
303
CHANGELOG.md
303
CHANGELOG.md
@ -1,6 +1,307 @@
|
|||||||
|
### v3.1.0 (_2025-03-25_)
|
||||||
|
|
||||||
|
- fix(bytecodePlugin): optimize 'use strict' directive replacement ([#681](https://github.com/alex8088/electron-vite/issues/681))
|
||||||
|
- perf: build compatilibity target for Electron 35 ([#729](https://github.com/alex8088/electron-vite/pull/729))
|
||||||
|
- chore(deps): update all non-major dependencies
|
||||||
|
- chore(deps): update globals to v16
|
||||||
|
- chore(deps): update esbuild to v0.25
|
||||||
|
|
||||||
|
### v3.1.0-beta.0 (_2025-03-12_)
|
||||||
|
|
||||||
|
See [v3.1.0-beta.0 changelog](https://github.com/alex8088/electron-vite/blob/v3.1.0-beta.0/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v3.0.0 (_2025-02-16_)
|
||||||
|
|
||||||
|
- feat: resolve conditions for preload
|
||||||
|
- perf: build compatilibity target for Electron 32
|
||||||
|
- perf: build compatilibity target for Electron 33 ([#651](https://github.com/alex8088/electron-vite/pull/651))
|
||||||
|
- perf: build compatilibity target for Electron 34
|
||||||
|
- chore: move to eslint flat config
|
||||||
|
- chore(deps): update all non-major dependencies
|
||||||
|
- chore(deps): update @rollup/plugin-node-resolve to v16
|
||||||
|
- chore(deps): update @rollup/plugin-typescript to v12
|
||||||
|
- chore(deps): update esbuild to v0.24
|
||||||
|
- chore(deps): update vite to v6
|
||||||
|
- chore(deps): update @type/node to v22
|
||||||
|
|
||||||
|
### v3.0.0-beta.0 (_2025-01-22_)
|
||||||
|
|
||||||
|
See [v3.0.0-beta.0 changelog](https://github.com/alex8088/electron-vite/blob/v3.0.0-beta.0/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v2.3.0 (_2024-06-23_)
|
||||||
|
|
||||||
|
- feat: resolve import.meta.\[dirname|filename\] to support CommonJS format
|
||||||
|
- fix: don't handle module ID that begin with \0 ([#530](https://github.com/alex8088/electron-vite/pull/530))
|
||||||
|
- fix: not using the mode from the config file ([#539](https://github.com/alex8088/electron-vite/pull/539))
|
||||||
|
- fix: default mode should not overrite user config mode
|
||||||
|
- perf: build compatilibity target for Electron 31
|
||||||
|
- perf: improve cjs shim
|
||||||
|
- chore(deps): update all non-major dependencies
|
||||||
|
- chore(deps): update @typescript-eslint/\* to v7
|
||||||
|
- chore(deps): update esbuild to v0.21
|
||||||
|
|
||||||
|
### v2.2.0 (_2024-04-21_)
|
||||||
|
|
||||||
|
- feat: export mergeConfig from vite ([#471](https://github.com/alex8088/electron-vite/issues/471))
|
||||||
|
- fix(types): narrow down the return type of defineConfig
|
||||||
|
- perf: build compatilibity target for Electron 30
|
||||||
|
- refactor(config): defineConfig types
|
||||||
|
- chore: fix camelcase typo
|
||||||
|
- chore: use rollup-plugin-rm to clean dist
|
||||||
|
|
||||||
|
### v2.1.0 (_2024-03-03_)
|
||||||
|
|
||||||
|
- feat: easy way to fork processes and use workers
|
||||||
|
- fix: config via build.lib fails when default entry point not found ([#393](https://github.com/alex8088/electron-vite/issues/393))
|
||||||
|
- perf: build compatilibity target for Electron 29
|
||||||
|
- perf: allow integrating more complex render solutions ([#412](https://github.com/alex8088/electron-vite/pull/412))
|
||||||
|
- perf(bytecodePlugin): warn that strings cannot be protected when minification is enabled ([#417](https://github.com/alex8088/electron-vite/issues/417))
|
||||||
|
|
||||||
|
### v2.0.0 (_2024-01-09_)
|
||||||
|
|
||||||
|
- feat: bump minimum node version to 18
|
||||||
|
- feat: migrate to ESM
|
||||||
|
- feat: support vite 5
|
||||||
|
- feat: add package.json to export map
|
||||||
|
- feat: support ESM in Electron
|
||||||
|
- feat: env variables prefixed with VITE\_ will be shared in main process and renderer
|
||||||
|
- feat: support for passing arguments to electron in dev and preview commands ([#339](https://github.com/alex8088/electron-vite/pull/339))
|
||||||
|
- feat: config file supports "type": "module" in package.json
|
||||||
|
- fix: emit assets when ssr is enabled
|
||||||
|
- fix: externalizeDepPlugin not work
|
||||||
|
- fix: electron's export subpaths also need to be externalized ([#372](https://github.com/alex8088/electron-vite/issues/372))
|
||||||
|
- perf: improve package.json resolve
|
||||||
|
- perf: use magic-string hires boundary for sourcemaps
|
||||||
|
- perf: build compatilibity target for Electron 28
|
||||||
|
- pref: resolve import meta url in CommonJS format
|
||||||
|
- perf(worker): ESM syntax
|
||||||
|
- perf: package version
|
||||||
|
- perf: dev error message
|
||||||
|
- perf(externalizeDepsPlugin): use cached package data to improve performance
|
||||||
|
- perf: loadEnv api also needs to load shared env variables prefixed with VITE\_
|
||||||
|
- refactor: build
|
||||||
|
- refactor: file hashes use url-safe base64 encoded hashes in vite 5 (rollup 4)
|
||||||
|
- refactor: remove Electron 11, 12 build compatilibity target
|
||||||
|
- refactor: use dynamic import directly
|
||||||
|
- build: use rollup-plugin-dts
|
||||||
|
- chore(deps): update all non-major dependencies
|
||||||
|
- chore(deps): update lint-staged to v15
|
||||||
|
- chore(deps): update eslint-config-prettier to v9
|
||||||
|
- chore(deps): update @rollup/plugin-typescript to v11
|
||||||
|
- chore(deps): update rollup to v4
|
||||||
|
- chore(deps): update vite to v5
|
||||||
|
- chore(deps): update esbuild to v0.19
|
||||||
|
- chore(deps): update typescript to 5.3.3
|
||||||
|
- chore: improve prettier config
|
||||||
|
- chore: update homepage
|
||||||
|
|
||||||
|
### v2.0.0-beta.4 (_2024-01-06_)
|
||||||
|
|
||||||
|
See [v2.0.0-beta.4 changelog](https://github.com/alex8088/electron-vite/blob/v2.0.0-beta.4/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v2.0.0-beta.3 (_2024-01-04_)
|
||||||
|
|
||||||
|
See [v2.0.0-beta.3 changelog](https://github.com/alex8088/electron-vite/blob/v2.0.0-beta.3/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v2.0.0-beta.2 (_2023-12-19_)
|
||||||
|
|
||||||
|
See [v2.0.0-beta.2 changelog](https://github.com/alex8088/electron-vite/blob/v2.0.0-beta.2/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v2.0.0-beta.1 (_2023-12-14_)
|
||||||
|
|
||||||
|
See [v2.0.0-beta.1 changelog](https://github.com/alex8088/electron-vite/blob/v2.0.0-beta.1/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v2.0.0-beta.0 (_2023-12-13_)
|
||||||
|
|
||||||
|
See [v2.0.0-beta.0 changelog](https://github.com/alex8088/electron-vite/blob/v2.0.0-beta.0/CHANGELOG.md)
|
||||||
|
|
||||||
|
### v1.0.29 (_2023-11-17_)
|
||||||
|
|
||||||
|
- feat(cli): support --noSandbox option for dev and preview command
|
||||||
|
- perf: build compatilibity target for Electron 27
|
||||||
|
|
||||||
|
### v1.0.28 (_2023-09-18_)
|
||||||
|
|
||||||
|
- feat(cli): supports specifying electron entry file ([#270](https://github.com/alex8088/electron-vite/issues/270))
|
||||||
|
- fix(externalizeDepsPlugin): supports subpath
|
||||||
|
- perf: build compatilibity target for Electron 26
|
||||||
|
- chore(types): add .json?commonjs-external&asset typing
|
||||||
|
|
||||||
|
### v1.0.27 (_2023-08-01_)
|
||||||
|
|
||||||
|
- chore: remove preinstall script
|
||||||
|
|
||||||
|
### v1.0.26 (_2023-07-30_)
|
||||||
|
|
||||||
|
- feat(cli): add CLI `--inspect[-brk]` to support debugging without IDEs ([#231](https://github.com/alex8088/electron-vite/issues/231))
|
||||||
|
- feat(types): add process.env.ELECTRON_RENDERER_URL type
|
||||||
|
- feat(types): add Vite importMeta types
|
||||||
|
- perf: spawn Electron process using parent's stdios ([#236](https://github.com/alex8088/electron-vite/issues/236))
|
||||||
|
- chore: update user config interface jsdoc
|
||||||
|
- chore(deps): update pnpm to v8
|
||||||
|
- chore(deps): update prettier to v3
|
||||||
|
- chore(deps): update @typescript-eslint/\* to v6
|
||||||
|
|
||||||
|
### v1.0.25 (_2023-07-11_)
|
||||||
|
|
||||||
|
- fix: remove node resolve condition for preload [#204](https://github.com/alex8088/electron-vite/issues/204)
|
||||||
|
- fix(asset): asset handling error when hot reloading
|
||||||
|
- chore(deps): update all non-major dependencies
|
||||||
|
- chore(deps): update fs-extra to v11
|
||||||
|
- chore(deps): update @types/node to v18
|
||||||
|
- chore(deps): update typescript to 5.0.4
|
||||||
|
- chore(deps): update vite to 4.4.2
|
||||||
|
- chore(deps): update esbuild to v0.18
|
||||||
|
- chore(deps): update rollup to 3.26.2
|
||||||
|
|
||||||
|
### v1.0.24 (_2023-06-25_)
|
||||||
|
|
||||||
|
- fix(bytecodePlugin): bytecode loader relative path is incorrect
|
||||||
|
- perf: ignore `browser` field and additional `node` condition for main config
|
||||||
|
|
||||||
|
### v1.0.23 (_2023-06-04_)
|
||||||
|
|
||||||
|
- feat: supports ES build target for renderer [#174](https://github.com/alex8088/electron-vite/issues/174)
|
||||||
|
- revert: chore: remove process env define [#159](https://github.com/alex8088/electron-vite/issues/174)
|
||||||
|
- perf: build compatilibity target for Electron 25
|
||||||
|
- chore(deps): update all non-major dependencies
|
||||||
|
|
||||||
|
### v1.0.22 (_2023-04-23_)
|
||||||
|
|
||||||
|
- feat(cli): add --rendererOnly flag to dev command
|
||||||
|
- perf: build compatilibity target for Electron 24
|
||||||
|
- chore: remove process env define
|
||||||
|
- chore: typo error messages
|
||||||
|
|
||||||
|
### v1.0.21 (_2023-03-27_)
|
||||||
|
|
||||||
|
- fix(bytecodePlugin): bytecode loader is not referenced correctly in the chunks
|
||||||
|
- fix(bytecodePlugin): sub-chunks are not compliled in vite 4
|
||||||
|
- perf: always disable build.modulePreload in main and preload config
|
||||||
|
- chore(deps): update esbuild to 0.17
|
||||||
|
- chore(deps): update vite to 4.2.1
|
||||||
|
|
||||||
|
### v1.0.20 (_2023-03-12_)
|
||||||
|
|
||||||
|
- feat: support for renderer debugging [#130](https://github.com/alex8088/electron-vite/issues/130)
|
||||||
|
- fix(asset): asset path is not resolved correctly when outDir is specified [#117](https://github.com/alex8088/electron-vite/issues/117)
|
||||||
|
- fix: specified renderer outDir is not parsed correctly
|
||||||
|
- fix(bytecodePlugin): not work in monorepo [#128](https://github.com/alex8088/electron-vite/issues/128)
|
||||||
|
- perf: build compatilibity target for Electron 23
|
||||||
|
- perf: print log
|
||||||
|
- chore(deps): update all non-major dependencies
|
||||||
|
- chore(deps): update vite to 4.1.4
|
||||||
|
- chore(deps): update rollup to 3.18
|
||||||
|
- chore(deps): update magic-string to 0.30.0
|
||||||
|
|
||||||
|
### v1.0.19 (_2023-02-06_)
|
||||||
|
|
||||||
|
- feat(bytecodePlugin): protect strings [#91](https://github.com/alex8088/electron-vite/issues/91)
|
||||||
|
- fix(bytecodePlugin): escape protected strings (thanks to [@jeremyben](https://github.com/jeremyben))
|
||||||
|
|
||||||
|
### v1.0.18 (_2023-01-16_)
|
||||||
|
|
||||||
|
- feat(asset): support for WebAssembly in the main process
|
||||||
|
- fix(asset): wasm must be suffixed with `?loader`
|
||||||
|
|
||||||
|
### v1.0.17 (_2023-01-08_)
|
||||||
|
|
||||||
|
- feat: static asset handling
|
||||||
|
- fix: output duplicate log in vscode debugging [#75](https://github.com/alex8088/electron-vite/issues/75)
|
||||||
|
- chore(bytecodePlugin): KiB to kB
|
||||||
|
- chore(worker): use toRelativePath helper
|
||||||
|
- chore(deps): update all non-major dependencies
|
||||||
|
- chore(deps): update vite to 4.0.4
|
||||||
|
|
||||||
|
### v1.0.16 (_2022-12-12_)
|
||||||
|
|
||||||
|
- feat: vite 4.x support [#69](https://github.com/alex8088/electron-vite/issues/69)
|
||||||
|
- fix: `NODE_ENV` is incorrect in vite 4.x [#70](https://github.com/alex8088/electron-vite/issues/70)
|
||||||
|
- fix: invalid output format check
|
||||||
|
- fix: output format check
|
||||||
|
- chore(deps): update all non-major dependencies
|
||||||
|
- chore(deps): update esbuild and magic-string
|
||||||
|
- chore(deps): update vite to 4.0.0
|
||||||
|
|
||||||
|
### v1.0.15 (_2022-12-05_)
|
||||||
|
|
||||||
|
- feat: support mode and command conditional config
|
||||||
|
- feat: specify env prefixes for vite's loadEnv and export it
|
||||||
|
- perf: build compatilibity target for Electron 22
|
||||||
|
- perf: do not externalize node builtin modules for the renderer [#61](https://github.com/alex8088/electron-vite/issues/61)
|
||||||
|
|
||||||
|
### v1.0.14 (_2022-11-13_)
|
||||||
|
|
||||||
|
- fix(bytecodePlugin): replace bytecode module regex
|
||||||
|
|
||||||
|
### v1.0.13 (_2022-11-11_)
|
||||||
|
|
||||||
|
- feat: support for node worker
|
||||||
|
- refactor: plugins
|
||||||
|
- fix(swcPlugin): unreachable code
|
||||||
|
- fix(bytecodePlugin): bytecode loader injection and chunk module parsing errors [#49](https://github.com/alex8088/electron-vite/issues/49)
|
||||||
|
- fix: incorrect replace `__dirname`/`__filename` in config file
|
||||||
|
- fix: output format error under multiple entries
|
||||||
|
|
||||||
|
### v1.0.12 (_2022-11-02_)
|
||||||
|
|
||||||
|
- feat: support monorepo (by @ianstormtaylor)
|
||||||
|
- feat: add `--skipBuild` flag to preview command
|
||||||
|
- feat: make a SWC plugin to support TypeScript decorators (`emitDecoratorMetadata`)
|
||||||
|
- fix: use `modulePreload.polyfill` instead `polyfillModulePreload`
|
||||||
|
- chore: update deps
|
||||||
|
|
||||||
|
### v1.0.11 (_2022-10-16_)
|
||||||
|
|
||||||
|
- feat: externalize deps plugin
|
||||||
|
|
||||||
|
### v1.0.10 (_2022-10-07_)
|
||||||
|
|
||||||
|
- feat: export splitVendorChunk from vite
|
||||||
|
- fix: compatible with the latest version of Electron
|
||||||
|
- refactor: load config file
|
||||||
|
- perf: the bytecodePlugin transform arrow function by default
|
||||||
|
- perf: disable gzip-compressed size reporting, increase build performance
|
||||||
|
- perf: bytecode compilation log print format
|
||||||
|
- perf: build compatilibity target for Electron 21
|
||||||
|
|
||||||
|
### v1.0.9 (_2022-09-19_)
|
||||||
|
|
||||||
|
- feat: source code protection
|
||||||
|
- fix: specify a config file error
|
||||||
|
- fix: in the specified non-production mode, the `base` path is wrong
|
||||||
|
- perf: cache electron executable path
|
||||||
|
- chore: add electron-vite mode node env
|
||||||
|
- chore: use node prefix
|
||||||
|
|
||||||
|
### v1.0.8 (_2022-09-11_)
|
||||||
|
|
||||||
|
- feat: the main process and preload scripts support hot reloading [#7](https://github.com/alex8088/electron-vite/issues/7)
|
||||||
|
|
||||||
|
### v1.0.7 (_2022-08-29_)
|
||||||
|
|
||||||
|
- feat: add sourcemap cli options for debugging
|
||||||
|
|
||||||
|
### v1.0.6 (_2022-08-25_)
|
||||||
|
|
||||||
|
- chore: update deps
|
||||||
|
- chore: use vite@3
|
||||||
|
|
||||||
|
### v1.0.5 (_2022-08-20_)
|
||||||
|
|
||||||
|
- fix: can not get import meta env variables in renderer
|
||||||
|
- feat: define env prefix
|
||||||
|
- feat: new official website released
|
||||||
|
|
||||||
|
### v1.0.4 (_2022-07-03_)
|
||||||
|
|
||||||
|
- fix: error occurs when the preload config is a function (by @Beiluola)
|
||||||
|
- fix: ELECTRON_RENDERER_URL is incorrect when host is true (by @Beiluola)
|
||||||
|
- feat: add sqlite3 module to rollup external option
|
||||||
|
|
||||||
### v1.0.3 (_2022-05-01_)
|
### v1.0.3 (_2022-05-01_)
|
||||||
|
|
||||||
- fix: throw error when vite.config.* file in root ([#3](https://github.com/alex8088/electron-vite/issues/3))
|
- fix: throw error when vite.config.\* file in root ([#3](https://github.com/alex8088/electron-vite/issues/3))
|
||||||
- feat: export splitVendorChunkPlugin from vite
|
- feat: export splitVendorChunkPlugin from vite
|
||||||
- perf: build target for Electron 19
|
- perf: build target for Electron 19
|
||||||
|
|
||||||
|
@ -14,8 +14,9 @@ pnpm install
|
|||||||
|
|
||||||
## Pull Request
|
## Pull Request
|
||||||
|
|
||||||
- Checkout a topic branch from a base branch, e.g. master, and merge back against that branch.
|
- Checkout a topic branch from a base branch, e.g. fix-bug, and merge back against that branch.
|
||||||
- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.
|
- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.
|
||||||
- To check that your contributions match the project coding style make sure `pnpm lint` && `pnpm typecheck` passes. To build project run: `pnpm build`.
|
- To check that your contributions match the project coding style make sure `pnpm lint` && `pnpm typecheck` passes. To build project run: `pnpm build`.
|
||||||
- Commit messages must follow the [commit message convention](./.github/commit-convention.md). Commit messages are automatically validated before commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks)).
|
- Commit messages must follow the [commit message convention](./.github/commit-convention.md). Commit messages are automatically validated before commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks)).
|
||||||
|
- Commit messages preferably in English.
|
||||||
- No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks)).
|
- No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks)).
|
||||||
|
330
README.md
330
README.md
@ -1,23 +1,41 @@
|
|||||||
# electron-vite
|
<p align="center">
|
||||||
|
<img src="https://alex8088.github.io/assets/electron-vite.svg" width="150px" height="150px">
|
||||||
<p>
|
|
||||||
<img src="https://img.shields.io/badge/node->14.0.0-blue.svg" alt="node" />
|
|
||||||
<img src="https://img.shields.io/badge/vite->2.6.0-747bff.svg" alt="vite" />
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
English | [简体中文](./README.zh-CN.md)
|
<div align="center">
|
||||||
|
<h1>electron-vite</h1>
|
||||||
|
</div>
|
||||||
|
<p align="center">Next generation Electron build tooling based on Vite</p>
|
||||||
|
|
||||||
> An Electron CLI integrated with Vite
|
<p align="center">
|
||||||
|
<img src="https://img.shields.io/npm/v/electron-vite?color=6988e6&label=version">
|
||||||
|
<img src="https://img.shields.io/github/license/alex8088/electron-vite?color=blue" alt="license" />
|
||||||
|
</p>
|
||||||
|
|
||||||
---
|
<p align="center">
|
||||||
|
<a href="https://electron-vite.org">Documentation</a> |
|
||||||
|
<a href="https://electron-vite.org/guide">Getting Started</a> |
|
||||||
|
<a href="https://github.com/alex8088/quick-start/tree/master/packages/create-electron">create-electron</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://cn.electron-vite.org">中文文档</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- ⚡️Use the same way as [Vite](https://vitejs.dev)
|
- ⚡️ [Vite](https://vitejs.dev) powered and use the same way.
|
||||||
- 🔨Main process, renderer process and preload script source code are built using Vite
|
- 🛠 Pre-configured for Electron, don't worry about configuration.
|
||||||
- 📃Main process, renderer process and preload script Vite configuration combined into one file
|
- 💡 Optimize asset handling (Node.js addons, WebAssembly, Worker Thread, etc).
|
||||||
- 📦Preset optimal build configuration
|
- 🚀 Fast HMR for renderer processes.
|
||||||
- 🚀HMR for renderer processes
|
- 🔥 Hot reloading for main process and preload scripts.
|
||||||
|
- 🔌 Easy to debug in IDEs like VSCode or WebStorm.
|
||||||
|
- 🔒 Compile to v8 bytecode to protect source code.
|
||||||
|
- 🏷️ Support for TypeScript decorators.
|
||||||
|
- 📦 Out-of-the-box support for TypeScript, Vue, React, Svelte, SolidJS and more.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -34,66 +52,14 @@ In a project where `electron-vite` is installed, you can use `electron-vite` bin
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron-vite preview", // start electron app to preview production build
|
"start": "electron-vite preview",
|
||||||
"dev": "electron-vite dev", // start dev server and electron app
|
"dev": "electron-vite dev",
|
||||||
"prebuild": "electron-vite build" // build for production
|
"prebuild": "electron-vite build"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In order to use the renderer process HMR, you need to use the `environment variables` to determine whether the window browser loads a local html file or a remote URL.
|
### Configuration
|
||||||
|
|
||||||
```js
|
|
||||||
function createWindow() {
|
|
||||||
// Create the browser window
|
|
||||||
const mainWindow = new BrowserWindow({
|
|
||||||
width: 800,
|
|
||||||
height: 600,
|
|
||||||
webPreferences: {
|
|
||||||
preload: path.join(__dirname, '../preload/index.js')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Load the remote URL for development or the local html file for production
|
|
||||||
if (!app.isPackaged && process.env['ELECTRON_RENDERER_URL']) {
|
|
||||||
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
|
||||||
} else {
|
|
||||||
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: For development, the renderer process `index.html` file needs to reference your script code via `<script type="module">`.
|
|
||||||
|
|
||||||
### Recommended project directory
|
|
||||||
|
|
||||||
```shell
|
|
||||||
├──src
|
|
||||||
│ ├──main
|
|
||||||
│ │ ├──index.js
|
|
||||||
│ │ └──...
|
|
||||||
│ ├──preload
|
|
||||||
│ │ ├──index.js
|
|
||||||
│ │ └──...
|
|
||||||
│ └──renderer
|
|
||||||
│ ├──src
|
|
||||||
│ ├──index.html
|
|
||||||
│ └──...
|
|
||||||
├──electron.vite.config.js
|
|
||||||
└──package.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get started
|
|
||||||
|
|
||||||
Clone the [electron-vite-boilerplate](https://github.com/alex8088/electron-vite-boilerplate) or use the [create-electron](https://github.com/alex8088/quick-start/tree/master/packages/create-electron) tool to scaffold your project.
|
|
||||||
|
|
||||||
``` bash
|
|
||||||
npm init @quick-start/electron
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configure
|
|
||||||
|
|
||||||
### Config file
|
|
||||||
|
|
||||||
When running `electron-vite` from the command line, electron-vite will automatically try to resolve a config file named `electron.vite.config.js` inside project root. The most basic config file looks like this:
|
When running `electron-vite` from the command line, electron-vite will automatically try to resolve a config file named `electron.vite.config.js` inside project root. The most basic config file looks like this:
|
||||||
|
|
||||||
@ -112,223 +78,27 @@ export default {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also explicitly specify a config file to use with the `--config` CLI option (resolved relative to `cwd`):
|
### Getting Started
|
||||||
|
|
||||||
```sh
|
Clone the [electron-vite-boilerplate](https://github.com/alex8088/electron-vite-boilerplate) or use the [create-electron](https://github.com/alex8088/quick-start/tree/master/packages/create-electron) tool to scaffold your project.
|
||||||
electron-vite --config my-config.js
|
|
||||||
|
```bash
|
||||||
|
npm create @quick-start/electron
|
||||||
```
|
```
|
||||||
|
|
||||||
**Tips**: `electron-vite` also supports `ts` or `mjs` config file.
|
Currently supported template presets include:
|
||||||
|
|
||||||
### Config intellisense
|
| JavaScript | TypeScript |
|
||||||
|
| :--------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------: |
|
||||||
|
| [vanilla](https://github.com/alex8088/quick-start/tree/master/packages/create-electron/playground/vanilla) | [vanilla-ts](https://github.com/alex8088/quick-start/tree/master/packages/create-electron/playground/vanilla-ts) |
|
||||||
|
| [vue](https://github.com/alex8088/quick-start/tree/master/packages/create-electron/playground/vue) | [vue-ts](https://github.com/alex8088/quick-start/tree/master/packages/create-electron/playground/vue-ts) |
|
||||||
|
| [react](https://github.com/alex8088/quick-start/tree/master/packages/create-electron/playground/react) | [react-ts](https://github.com/alex8088/quick-start/tree/master/packages/create-electron/playground/react-ts) |
|
||||||
|
| [svelte](https://github.com/alex8088/quick-start/tree/master/packages/create-electron/playground/svelte) | [svelte-ts](https://github.com/alex8088/quick-start/tree/master/packages/create-electron/playground/svelte-ts) |
|
||||||
|
| [solid](https://github.com/alex8088/quick-start/tree/master/packages/create-electron/playground/solid) | [solid-ts](https://github.com/alex8088/quick-start/tree/master/packages/create-electron/playground/solid-ts) |
|
||||||
|
|
||||||
Since `electron-vite` ships with TypeScript typings, you can leverage your IDE's intellisense with jsdoc type hints:
|
## Contribution
|
||||||
|
|
||||||
```js
|
See [Contributing Guide](CONTRIBUTING.md).
|
||||||
/**
|
|
||||||
* @type {import('electron-vite').UserConfig}
|
|
||||||
*/
|
|
||||||
const config = {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can use the `defineConfig` and `defineViteConfig` helper which should provide intellisense without the need for jsdoc annotations:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { defineConfig, defineViteConfig } from 'electron-vite'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
main: {
|
|
||||||
// ...
|
|
||||||
},
|
|
||||||
preload: {
|
|
||||||
// ...
|
|
||||||
},
|
|
||||||
renderer: defineViteConfig(({ command, mode }) => {
|
|
||||||
// conditional config use defineViteConfig
|
|
||||||
// ...
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tips**: The `defineViteConfig` exports from `Vite`.
|
|
||||||
|
|
||||||
### Config reference
|
|
||||||
|
|
||||||
See [vitejs.dev](https://vitejs.dev/config)
|
|
||||||
|
|
||||||
### Config presets
|
|
||||||
|
|
||||||
#### Build options for `main`:
|
|
||||||
|
|
||||||
- **outDir**: `out\main`(relative to project root)
|
|
||||||
- **target**: `node*`, automatically match node target of `Electron`. For example, the node target of Electron 17 is `node16.13`
|
|
||||||
- **lib.entry**: `src\main\{index|main}.{js|ts|mjs|cjs}`(relative to project root), empty string if not found
|
|
||||||
- **lib.formats**: `cjs`
|
|
||||||
- **rollupOptions.external**: `electron` and all builtin modules
|
|
||||||
|
|
||||||
#### Build options for `preload`:
|
|
||||||
|
|
||||||
- **outDir**: `out\preload`(relative to project root)
|
|
||||||
- **target**: the same as `main`
|
|
||||||
- **lib.entry**: `src\preload\{index|preload}.{js|ts|mjs|cjs}`(relative to project root), empty string if not found
|
|
||||||
- **lib.formats**: `cjs`
|
|
||||||
- **rollupOptions.external**: the same as `main`
|
|
||||||
|
|
||||||
#### Build options for `renderer`:
|
|
||||||
|
|
||||||
- **root**: `src\renderer`(relative to project root)
|
|
||||||
- **outDir**: `out\renderer`(relative to project root)
|
|
||||||
- **target**: `chrome*`, automatically match chrome target of `Electron`. For example, the chrome target of Electron 17 is `chrome98`
|
|
||||||
- **lib.entry**: `src\renderer\index.html`(relative to project root), empty string if not found
|
|
||||||
- **polyfillModulePreload**: `false`, there is no need to polyfill `Module Preload` for the Electron renderer
|
|
||||||
- **rollupOptions.external**: the same as `main`
|
|
||||||
|
|
||||||
#### Define option for `main` and `preload`
|
|
||||||
|
|
||||||
In web development, Vite will transform `'process.env.'` to `'({}).'`. This is reasonable and correct. But in nodejs development, we sometimes need to use `process.env`, so `electron-vite` will automatically add config define field to redefine global variable replacements like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default {
|
|
||||||
main: {
|
|
||||||
define: {
|
|
||||||
'process.env': 'process.env'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: If you want to use these configurations in an existing project, please see the Vite plugin [vite-plugin-electron-config](https://github.com/alex8088/vite-plugin-electron-config)
|
|
||||||
|
|
||||||
### Config FAQs
|
|
||||||
|
|
||||||
#### How do I configure when the Electron app has multiple windows?
|
|
||||||
|
|
||||||
When your electron app has multiple windows, it means there are multiple html files or preload files. You can modify your config file like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default {
|
|
||||||
main: {},
|
|
||||||
preload: {
|
|
||||||
build: {
|
|
||||||
rollupOptions: {
|
|
||||||
input: {
|
|
||||||
browser: resolve(__dirname, 'src/preload/browser.ts'),
|
|
||||||
webview: resolve(__dirname, 'src/preload/webview.ts')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderer: {
|
|
||||||
build: {
|
|
||||||
rollupOptions: {
|
|
||||||
input: {
|
|
||||||
browser: resolve(__dirname, 'src/renderer/browser.html'),
|
|
||||||
webview: resolve(__dirname, 'src/renderer/webview.html')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## CLI options
|
|
||||||
|
|
||||||
For the full list of CLI options, you can run `npx electron-vite -h` in your project. The flags listed below are only available via the command line interface:
|
|
||||||
|
|
||||||
- `--ignoreConfigWarning`: boolean, allow you ignore warning when config missing
|
|
||||||
- `--outDir`: string, output directory (default: out)
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
### build
|
|
||||||
|
|
||||||
Type Signature:
|
|
||||||
|
|
||||||
```js
|
|
||||||
async function build(inlineConfig: InlineConfig = {}): Promise<void>
|
|
||||||
```
|
|
||||||
|
|
||||||
Example Usage:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const path = require('path')
|
|
||||||
const { build } = require('electron-vite')
|
|
||||||
|
|
||||||
;(async () => {
|
|
||||||
await build({
|
|
||||||
build: {
|
|
||||||
outDir: 'out'
|
|
||||||
rollupOptions: {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})()
|
|
||||||
```
|
|
||||||
|
|
||||||
### createServer
|
|
||||||
|
|
||||||
Type Signature:
|
|
||||||
|
|
||||||
```js
|
|
||||||
async function createServer(inlineConfig: InlineConfig = {}): Promise<void>
|
|
||||||
```
|
|
||||||
|
|
||||||
Example Usage:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const { createServer } = require('electron-vite')
|
|
||||||
|
|
||||||
;(async () => {
|
|
||||||
await createServer({
|
|
||||||
server: {
|
|
||||||
port: 1337
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})()
|
|
||||||
```
|
|
||||||
|
|
||||||
### preview
|
|
||||||
|
|
||||||
Type Signature:
|
|
||||||
|
|
||||||
```js
|
|
||||||
async function preview(inlineConfig: InlineConfig = {}): Promise<void>
|
|
||||||
```
|
|
||||||
|
|
||||||
Example Usage:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const { preview } = require('electron-vite')
|
|
||||||
|
|
||||||
;(async () => {
|
|
||||||
await preview({})
|
|
||||||
})()
|
|
||||||
```
|
|
||||||
|
|
||||||
### InlineConfig
|
|
||||||
|
|
||||||
The InlineConfig interface extends Vite [UserConfig](https://vitejs.dev/guide/api-javascript.html#inlineconfig) with additional properties:
|
|
||||||
|
|
||||||
- `ignoreConfigWarning`: set to `false` to ignore warning when config missing
|
|
||||||
|
|
||||||
And omit `base` property because it is not necessary to set the base public path in Electron.
|
|
||||||
|
|
||||||
### resolveConfig
|
|
||||||
|
|
||||||
Type Signature:
|
|
||||||
|
|
||||||
```js
|
|
||||||
async function resolveConfig(
|
|
||||||
inlineConfig: InlineConfig,
|
|
||||||
command: 'build' | 'serve',
|
|
||||||
defaultMode = 'development'
|
|
||||||
): Promise<ResolvedConfig>
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
335
README.zh-CN.md
335
README.zh-CN.md
@ -1,335 +0,0 @@
|
|||||||
# electron-vite
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<img src="https://img.shields.io/badge/node->14.0.0-blue.svg" alt="node" />
|
|
||||||
<img src="https://img.shields.io/badge/vite->2.6.0-747bff.svg" alt="vite" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[English](./README.md) | 简体中文
|
|
||||||
|
|
||||||
> 新一代 Electron 开发构建工具
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 特性
|
|
||||||
|
|
||||||
- ⚡️使用方式与 [Vite](https://vitejs.dev) 相同
|
|
||||||
- 🔨主进程/渲染进程/ preload 脚本都使用 Vite 构建
|
|
||||||
- 📃统一所有配置,合并到一个文件中
|
|
||||||
- 📦预设构建配置,无需关注配置
|
|
||||||
- 🚀支持渲染进程热更新(HMR)
|
|
||||||
|
|
||||||
## 用法
|
|
||||||
|
|
||||||
### 安装
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm i electron-vite -D
|
|
||||||
```
|
|
||||||
|
|
||||||
### 开发 & 编译
|
|
||||||
|
|
||||||
在安装了 `electron-vite` 的项目中,可以直接使用 `npx electron-vite` 运行, 也可以在 `package.json` 文件中添加 npm scripts :
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"scripts": {
|
|
||||||
"start": "electron-vite preview", // 开启 Electron 预览生产构建
|
|
||||||
"dev": "electron-vite dev", // 开启开发服务并启动 Electron 程序
|
|
||||||
"prebuild": "electron-vite build" // 为生产打包构建
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
为了使用热更新(HMR),需要使用环境变量(`ELECTRON_RENDERER_URL`)来决定 Electron 窗口加载本地页面还是远程页面。
|
|
||||||
|
|
||||||
```js
|
|
||||||
function createWindow() {
|
|
||||||
// 创建窗口
|
|
||||||
const mainWindow = new BrowserWindow({
|
|
||||||
width: 800,
|
|
||||||
height: 600,
|
|
||||||
webPreferences: {
|
|
||||||
preload: path.join(__dirname, '../preload/index.js')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 开发模式下使用支持HMR的远程地址,生产模式下使用本地html页面
|
|
||||||
if (!app.isPackaged && process.env['ELECTRON_RENDERER_URL']) {
|
|
||||||
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
|
||||||
} else {
|
|
||||||
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**注意**:在开发中,渲染进程 `index.html` 文件需要通过 `<script type="module">` 引用脚本。
|
|
||||||
|
|
||||||
### 推荐项目目录
|
|
||||||
|
|
||||||
```shell
|
|
||||||
├──src
|
|
||||||
│ ├──main
|
|
||||||
│ │ ├──index.js
|
|
||||||
│ │ └──...
|
|
||||||
│ ├──preload
|
|
||||||
│ │ ├──index.js
|
|
||||||
│ │ └──...
|
|
||||||
│ └──renderer
|
|
||||||
│ ├──src
|
|
||||||
│ ├──index.html
|
|
||||||
│ └──...
|
|
||||||
├──electron.vite.config.js
|
|
||||||
└──package.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### 开始学习
|
|
||||||
|
|
||||||
克隆 [electron-vite-boilerplate](https://github.com/alex8088/electron-vite-boilerplate) 模板或者使用 [create-electron](https://github.com/alex8088/quick-start/tree/master/packages/create-electron) 脚手架来搭建项目学习。
|
|
||||||
|
|
||||||
``` bash
|
|
||||||
npm init @quick-start/electron
|
|
||||||
```
|
|
||||||
|
|
||||||
## 配置
|
|
||||||
|
|
||||||
### 配置文件
|
|
||||||
|
|
||||||
当以命令行方式运行 `electron-vite` 时,将会自动尝试解析项目根目录中名为 `electron.vite.config.js` 的配置文件。最基本的配置文件如下所示:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// electron.vite.config.js
|
|
||||||
export default {
|
|
||||||
main: {
|
|
||||||
// vite 配置选项
|
|
||||||
},
|
|
||||||
preload: {
|
|
||||||
// vite 配置选项
|
|
||||||
},
|
|
||||||
renderer: {
|
|
||||||
// vite 配置选项
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
你可以显式地通过 `--config` 命令行选项指定一个配置文件(相对于 `cwd` 路径进行解析):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
electron-vite --config my-config.js
|
|
||||||
```
|
|
||||||
|
|
||||||
**提示**:`electron-vite` 也支持 `ts` 或者 `mjs` 的配置文件。
|
|
||||||
|
|
||||||
### 配置智能提示
|
|
||||||
|
|
||||||
因为 `electron-vite` 本身附带 Typescript 类型,所以你可以通过 IDE 和 jsdoc 的配合来实现智能提示:
|
|
||||||
|
|
||||||
```js
|
|
||||||
/**
|
|
||||||
* @type {import('electron-vite').UserConfig}
|
|
||||||
*/
|
|
||||||
const config = {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config
|
|
||||||
```
|
|
||||||
|
|
||||||
你还可以使用 `defineConfig` and `defineViteConfig` 工具函数,这样不用 jsdoc 注解也可以获取类型提示:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { defineConfig, defineViteConfig } from 'electron-vite'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
main: {
|
|
||||||
// ...
|
|
||||||
},
|
|
||||||
preload: {
|
|
||||||
// ...
|
|
||||||
},
|
|
||||||
renderer: defineViteConfig(({ command, mode }) => {
|
|
||||||
// 条件配置可使用 defineViteConfig
|
|
||||||
// ...
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
**提示**:`defineViteConfig` 从 `Vite` 中导出。
|
|
||||||
|
|
||||||
### 配置参考
|
|
||||||
|
|
||||||
见 [vitejs.dev](https://vitejs.dev/config)
|
|
||||||
|
|
||||||
### 配置预设
|
|
||||||
|
|
||||||
#### `主进程`编译项预设:
|
|
||||||
|
|
||||||
- **outDir**:`out\main`(相对于根目录)
|
|
||||||
- **target**:`node*`,自动匹配 `Electron` 的 `node` 构建目标,如 Electron 17 为 `node16.13`
|
|
||||||
- **lib.entry**:`src\main\{index|main}.{js|ts|mjs|cjs}`(相对于根目录),找不到则为空
|
|
||||||
- **lib.formats**:`cjs`
|
|
||||||
- **rollupOptions.external**:`electron` 和所有内置 node 模块(如果用户配置了外部模块ID,将自动合并)
|
|
||||||
|
|
||||||
#### `preload` 脚本编译项预设:
|
|
||||||
|
|
||||||
- **outDir**:`out\preload`(相对于根目录)
|
|
||||||
- **target**:同`主进程`
|
|
||||||
- **lib.entry**:`src\preload\{index|preload}.{js|ts|mjs|cjs}`(相对于根目录),找不到则为空
|
|
||||||
- **lib.formats**:`cjs`
|
|
||||||
- **rollupOptions.external**:同`主进程`
|
|
||||||
|
|
||||||
#### `渲染进程`编译项预设:
|
|
||||||
|
|
||||||
- **root**:`src\renderer`(相对于根目录)
|
|
||||||
- **outDir**:`out\renderer`(相对于根目录)
|
|
||||||
- **target**:`chrome*`, 自动匹配 `Electron` 的 `chrome` 构建目标,如 Electron 17 为 `chrome98`
|
|
||||||
- **lib.entry**:`src\renderer\index.html`(相对于根目录),找不到则为空
|
|
||||||
- **polyfillModulePreload**:`false`,不需要为渲染进程 polyfill `Module Preload`
|
|
||||||
- **rollupOptions.external**:同`主进程`
|
|
||||||
|
|
||||||
#### `主进程`和 `preload` 脚本的 `define` 项设置:
|
|
||||||
|
|
||||||
在 Web 开发中,Vite 会将 `'process.env.'` 替换为 `'({}).'`,这是合理和正确的。但在 nodejs 开发中,我们有时候需要使用 `process.env` ,所以 `electron-vite` 重新预设全局变量替换,恢复其使用,预设如下:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default {
|
|
||||||
main: {
|
|
||||||
define: {
|
|
||||||
'process.env': 'process.env'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**提示**:如果你想在已有的项目中使用这些预设配置,可以使用 Vite 的插件 [vite-plugin-electron-config](https://github.com/alex8088/vite-plugin-electron-config)
|
|
||||||
|
|
||||||
### 配置问题
|
|
||||||
|
|
||||||
#### 如果 Electron 具有多窗口应该如何配置?
|
|
||||||
|
|
||||||
当 Electron 应用程序具有多窗口时,就意味着可能有多个 html 页面和 preload 脚本,你可以像下面一样修改你的配置文件:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default {
|
|
||||||
main: {},
|
|
||||||
preload: {
|
|
||||||
build: {
|
|
||||||
rollupOptions: {
|
|
||||||
input: {
|
|
||||||
browser: resolve(__dirname, 'src/preload/browser.ts'),
|
|
||||||
webview: resolve(__dirname, 'src/preload/webview.ts')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderer: {
|
|
||||||
build: {
|
|
||||||
rollupOptions: {
|
|
||||||
input: {
|
|
||||||
browser: resolve(__dirname, 'src/renderer/browser.html'),
|
|
||||||
webview: resolve(__dirname, 'src/renderer/webview.html')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 命令行选项
|
|
||||||
|
|
||||||
在项目中,可运行 `npx electron-vite -h` 获得完整的命令行选项列表。下面列出的标志只能通过命令行使用:
|
|
||||||
|
|
||||||
- `--ignoreConfigWarning`:boolean,忽略配置缺失警告(如配置文件中移除 preload 配置,不使用 preload 开发时,是有用的)
|
|
||||||
- `--outDir`:string,输出路径,相对根目录 (默认:out)
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
### build
|
|
||||||
|
|
||||||
类型:
|
|
||||||
|
|
||||||
```js
|
|
||||||
async function build(inlineConfig: InlineConfig = {}): Promise<void>
|
|
||||||
```
|
|
||||||
|
|
||||||
示例:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const path = require('path')
|
|
||||||
const { build } = require('electron-vite')
|
|
||||||
|
|
||||||
;(async () => {
|
|
||||||
await build({
|
|
||||||
build: {
|
|
||||||
outDir: 'out'
|
|
||||||
rollupOptions: {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})()
|
|
||||||
```
|
|
||||||
|
|
||||||
### createServer
|
|
||||||
|
|
||||||
类型:
|
|
||||||
|
|
||||||
```js
|
|
||||||
async function createServer(inlineConfig: InlineConfig = {}): Promise<void>
|
|
||||||
```
|
|
||||||
|
|
||||||
示例:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const { createServer } = require('electron-vite')
|
|
||||||
|
|
||||||
;(async () => {
|
|
||||||
await createServer({
|
|
||||||
server: {
|
|
||||||
port: 1337
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})()
|
|
||||||
```
|
|
||||||
|
|
||||||
### preview
|
|
||||||
|
|
||||||
类型:
|
|
||||||
|
|
||||||
```js
|
|
||||||
async function preview(inlineConfig: InlineConfig = {}): Promise<void>
|
|
||||||
```
|
|
||||||
|
|
||||||
示例:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const { preview } = require('electron-vite')
|
|
||||||
|
|
||||||
;(async () => {
|
|
||||||
await preview({})
|
|
||||||
})()
|
|
||||||
```
|
|
||||||
|
|
||||||
### InlineConfig
|
|
||||||
|
|
||||||
`InlineConfig` 接口扩展了 Vite [UserConfig](https://vitejs.dev/guide/api-javascript.html#inlineconfig) 并添加了以下属性:
|
|
||||||
|
|
||||||
- `ignoreConfigWarning`:设置为 `false` 来忽略配置缺失警告
|
|
||||||
|
|
||||||
同时移除 `base` 属性,因为在 Electron 中没有必要指定公共基础路径。
|
|
||||||
|
|
||||||
### resolveConfig
|
|
||||||
|
|
||||||
类型:
|
|
||||||
|
|
||||||
```js
|
|
||||||
async function resolveConfig(
|
|
||||||
inlineConfig: InlineConfig,
|
|
||||||
command: 'build' | 'serve',
|
|
||||||
defaultMode = 'development'
|
|
||||||
): Promise<ResolvedConfig>
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[MIT](./LICENSE) © alex.wei
|
|
@ -1,52 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
|
||||||
|
|
||||||
"mainEntryPointFilePath": "./dist/types/index.d.ts",
|
|
||||||
|
|
||||||
"dtsRollup": {
|
|
||||||
"enabled": true,
|
|
||||||
"untrimmedFilePath": "",
|
|
||||||
"publicTrimmedFilePath": "./dist/index.d.ts"
|
|
||||||
},
|
|
||||||
|
|
||||||
"apiReport": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"docModel": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"tsdocMetadata": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"messages": {
|
|
||||||
"compilerMessageReporting": {
|
|
||||||
"default": {
|
|
||||||
"logLevel": "warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"extractorMessageReporting": {
|
|
||||||
"default": {
|
|
||||||
"logLevel": "warning",
|
|
||||||
"addToApiReportFile": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"ae-missing-release-tag": {
|
|
||||||
"logLevel": "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"tsdocMessageReporting": {
|
|
||||||
"default": {
|
|
||||||
"logLevel": "warning"
|
|
||||||
},
|
|
||||||
|
|
||||||
"tsdoc-undefined-tag": {
|
|
||||||
"logLevel": "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
32
bin/electron-bytecode.cjs
Normal file
32
bin/electron-bytecode.cjs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
const vm = require('vm')
|
||||||
|
const v8 = require('v8')
|
||||||
|
const wrap = require('module').wrap
|
||||||
|
|
||||||
|
v8.setFlagsFromString('--no-lazy')
|
||||||
|
v8.setFlagsFromString('--no-flush-bytecode')
|
||||||
|
|
||||||
|
let code = ''
|
||||||
|
|
||||||
|
process.stdin.setEncoding('utf-8')
|
||||||
|
|
||||||
|
process.stdin.on('readable', () => {
|
||||||
|
const data = process.stdin.read()
|
||||||
|
if (data !== null) {
|
||||||
|
code += data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
process.stdin.on('end', () => {
|
||||||
|
try {
|
||||||
|
if (typeof code !== 'string') {
|
||||||
|
throw new Error(`javascript code must be string. ${typeof code} was given.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = new vm.Script(wrap(code), { produceCachedData: true })
|
||||||
|
const bytecodeBuffer = script.createCachedData()
|
||||||
|
|
||||||
|
process.stdout.write(bytecodeBuffer)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
})
|
@ -24,7 +24,7 @@ if (debugIndex > 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function run() {
|
function run() {
|
||||||
require('../dist/cli')
|
import('../dist/cli.mjs')
|
||||||
}
|
}
|
||||||
|
|
||||||
run()
|
run()
|
||||||
|
59
eslint.config.js
Normal file
59
eslint.config.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// ts-check
|
||||||
|
|
||||||
|
import eslint from '@eslint/js'
|
||||||
|
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||||
|
import globals from 'globals'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{ ignores: ['**/node_modules', '**/dist', '**/bin'] },
|
||||||
|
eslint.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
eslintPluginPrettierRecommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parser: tseslint.parser,
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 2022
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.es2021,
|
||||||
|
...globals.node
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': 'warn',
|
||||||
|
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': 'allow-with-description' }],
|
||||||
|
'@typescript-eslint/explicit-function-return-type': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
allowExpressions: true,
|
||||||
|
allowTypedFunctionExpressions: true,
|
||||||
|
allowHigherOrderFunctions: true,
|
||||||
|
allowIIFEs: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-empty-function': ['error', { allow: ['arrowFunctions'] }],
|
||||||
|
'@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'always' }],
|
||||||
|
'@typescript-eslint/no-explicit-any': 'error',
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
|
'@typescript-eslint/no-require-imports': 'error',
|
||||||
|
'@typescript-eslint/no-unused-expressions': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
allowShortCircuit: true,
|
||||||
|
allowTaggedTemplates: true,
|
||||||
|
allowTernary: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.js', '*.mjs'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
112
node.d.ts
vendored
Normal file
112
node.d.ts
vendored
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// node worker
|
||||||
|
declare module '*?nodeWorker' {
|
||||||
|
import { Worker, WorkerOptions } from 'node:worker_threads'
|
||||||
|
export default function (options: WorkerOptions): Worker
|
||||||
|
}
|
||||||
|
|
||||||
|
// module path
|
||||||
|
declare module '*?modulePath' {
|
||||||
|
const src: string
|
||||||
|
export default src
|
||||||
|
}
|
||||||
|
|
||||||
|
// node asset
|
||||||
|
declare module '*?asset' {
|
||||||
|
const src: string
|
||||||
|
export default src
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*?asset&asarUnpack' {
|
||||||
|
const src: string
|
||||||
|
export default src
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.json?commonjs-external&asset' {
|
||||||
|
const src: string
|
||||||
|
export default src
|
||||||
|
}
|
||||||
|
|
||||||
|
// native node module
|
||||||
|
declare module '*.node' {
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
|
const node: any
|
||||||
|
export default node
|
||||||
|
}
|
||||||
|
|
||||||
|
// node wasm
|
||||||
|
declare module '*.wasm?loader' {
|
||||||
|
const loadWasm: (options?: WebAssembly.Imports) => Promise<WebAssembly.Instance>
|
||||||
|
export default loadWasm
|
||||||
|
}
|
||||||
|
|
||||||
|
// build-in process env
|
||||||
|
declare namespace NodeJS {
|
||||||
|
interface ProcessEnv {
|
||||||
|
/**
|
||||||
|
* Vite's dev server address for Electron renderers.
|
||||||
|
*/
|
||||||
|
readonly ELECTRON_RENDERER_URL?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refer to Vite's ImportMeta type declarations
|
||||||
|
// <https://github.com/vitejs/vite/blob/main/packages/vite/types/importMeta.d.ts>
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
MODE: string
|
||||||
|
DEV: boolean
|
||||||
|
PROD: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportGlobOptions<Eager extends boolean, AsType extends string> {
|
||||||
|
/**
|
||||||
|
* Import type for the import url.
|
||||||
|
*/
|
||||||
|
as?: AsType
|
||||||
|
/**
|
||||||
|
* Import as static or dynamic
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
eager?: Eager
|
||||||
|
/**
|
||||||
|
* Import only the specific named export. Set to `default` to import the default export.
|
||||||
|
*/
|
||||||
|
import?: string
|
||||||
|
/**
|
||||||
|
* Custom queries
|
||||||
|
*/
|
||||||
|
query?: string | Record<string, string | number | boolean>
|
||||||
|
/**
|
||||||
|
* Search files also inside `node_modules/` and hidden directories (e.g. `.git/`). This might have impact on performance.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
exhaustive?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KnownAsTypeMap {
|
||||||
|
raw: string
|
||||||
|
url: string
|
||||||
|
worker: Worker
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportGlobFunction {
|
||||||
|
/**
|
||||||
|
* Import a list of files with a glob pattern.
|
||||||
|
*
|
||||||
|
* https://vitejs.dev/guide/features.html#glob-import
|
||||||
|
*/
|
||||||
|
<Eager extends boolean, As extends string, T = As extends keyof KnownAsTypeMap ? KnownAsTypeMap[As] : unknown>(
|
||||||
|
glob: string | string[],
|
||||||
|
options?: ImportGlobOptions<Eager, As>
|
||||||
|
): (Eager extends true ? true : false) extends true ? Record<string, T> : Record<string, () => Promise<T>>
|
||||||
|
<M>(glob: string | string[], options?: ImportGlobOptions<false, string>): Record<string, () => Promise<M>>
|
||||||
|
<M>(glob: string | string[], options: ImportGlobOptions<true, string>): Record<string, M>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
url: string
|
||||||
|
readonly env: ImportMetaEnv
|
||||||
|
glob: ImportGlobFunction
|
||||||
|
}
|
85
package.json
85
package.json
@ -1,19 +1,34 @@
|
|||||||
{
|
{
|
||||||
"name": "electron-vite",
|
"name": "electron-vite",
|
||||||
"version": "1.0.3",
|
"version": "3.1.0",
|
||||||
"description": "Use vite for your electron app.",
|
"description": "Electron build tooling based on Vite",
|
||||||
"main": "dist/index.js",
|
"type": "module",
|
||||||
|
"main": "dist/index.cjs",
|
||||||
|
"module": "dist/index.mjs",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.mjs",
|
||||||
|
"require": "./dist/index.cjs"
|
||||||
|
},
|
||||||
|
"./node": {
|
||||||
|
"types": "./node.d.ts"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"electron-vite": "bin/electron-vite.js"
|
"electron-vite": "bin/electron-vite.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"bin",
|
"bin",
|
||||||
"dist"
|
"dist",
|
||||||
|
"node.d.ts"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.2.0"
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
},
|
},
|
||||||
|
"packageManager": "pnpm@8.6.10",
|
||||||
"author": "Alex Wei<https://github.com/alex8088>",
|
"author": "Alex Wei<https://github.com/alex8088>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -23,7 +38,7 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/alex8088/electron-vite/issues"
|
"url": "https://github.com/alex8088/electron-vite/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/alex8088/electron-vite#readme",
|
"homepage": "https://electron-vite.org",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"electron",
|
"electron",
|
||||||
"vite",
|
"vite",
|
||||||
@ -32,9 +47,9 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"lint": "eslint --ext .ts src/**",
|
"lint": "eslint --cache .",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"build": "npm run lint && node scripts/build.js"
|
"build": "pnpm run lint && rollup -c rollup.config.ts --configPlugin typescript"
|
||||||
},
|
},
|
||||||
"simple-git-hooks": {
|
"simple-git-hooks": {
|
||||||
"pre-commit": "npx lint-staged",
|
"pre-commit": "npx lint-staged",
|
||||||
@ -50,30 +65,42 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vite": "^2.9.0"
|
"@swc/core": "^1.0.0",
|
||||||
|
"vite": "^4.0.0 || ^5.0.0 || ^6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@swc/core": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/api-extractor": "^7.19.4",
|
"@eslint/js": "^9.22.0",
|
||||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
"@rollup/plugin-typescript": "^8.3.0",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
"@types/node": "16.11.22",
|
"@rollup/plugin-typescript": "^12.1.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.11.0",
|
"@swc/core": "^1.11.9",
|
||||||
"@typescript-eslint/parser": "^5.11.0",
|
"@types/node": "^22.13.10",
|
||||||
"eslint": "^8.7.0",
|
"eslint": "^9.22.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^10.1.1",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
"fs-extra": "^10.0.0",
|
"globals": "^16.0.0",
|
||||||
"lint-staged": "^12.3.6",
|
"lint-staged": "^15.4.3",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^3.5.3",
|
||||||
"rollup": "^2.64.0",
|
"rollup": "^4.35.0",
|
||||||
"simple-git-hooks": "^2.7.0",
|
"rollup-plugin-dts": "^6.1.1",
|
||||||
"tslib": "^2.3.1",
|
"rollup-plugin-rm": "^1.0.2",
|
||||||
"typescript": "^4.5.5",
|
"simple-git-hooks": "^2.11.1",
|
||||||
"vite": "^2.9.6"
|
"tslib": "^2.8.1",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"typescript-eslint": "^8.26.1",
|
||||||
|
"vite": "^6.2.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cac": "^6.7.12",
|
"@babel/core": "^7.26.10",
|
||||||
"esbuild": "^0.14.38",
|
"@babel/plugin-transform-arrow-functions": "^7.25.9",
|
||||||
"picocolors": "^1.0.0"
|
"cac": "^6.7.14",
|
||||||
|
"esbuild": "^0.25.1",
|
||||||
|
"magic-string": "^0.30.17",
|
||||||
|
"picocolors": "^1.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2905
pnpm-lock.yaml
generated
2905
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
47
rollup.config.ts
Normal file
47
rollup.config.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { createRequire } from 'node:module'
|
||||||
|
import { defineConfig } from 'rollup'
|
||||||
|
import ts from '@rollup/plugin-typescript'
|
||||||
|
import resolve from '@rollup/plugin-node-resolve'
|
||||||
|
import json from '@rollup/plugin-json'
|
||||||
|
import dts from 'rollup-plugin-dts'
|
||||||
|
import rm from 'rollup-plugin-rm'
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url)
|
||||||
|
const pkg = require('./package.json')
|
||||||
|
|
||||||
|
const external = ['esbuild', ...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})]
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{
|
||||||
|
input: ['src/index.ts', 'src/cli.ts'],
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
dir: 'dist',
|
||||||
|
entryFileNames: '[name].cjs',
|
||||||
|
chunkFileNames: 'chunks/lib-[hash].cjs',
|
||||||
|
format: 'cjs'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dir: 'dist',
|
||||||
|
entryFileNames: '[name].mjs',
|
||||||
|
chunkFileNames: 'chunks/lib-[hash].mjs',
|
||||||
|
format: 'es'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
external,
|
||||||
|
plugins: [
|
||||||
|
rm('dist', 'buildStart'),
|
||||||
|
json(),
|
||||||
|
ts({ compilerOptions: { rootDir: 'src', declaration: true, declarationDir: 'dist/types' } }),
|
||||||
|
resolve()
|
||||||
|
],
|
||||||
|
treeshake: {
|
||||||
|
moduleSideEffects: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'dist/types/index.d.ts',
|
||||||
|
output: [{ file: pkg.types, format: 'es' }],
|
||||||
|
plugins: [dts(), rm('dist/types', 'buildEnd')]
|
||||||
|
}
|
||||||
|
])
|
@ -1,67 +0,0 @@
|
|||||||
const path = require('path')
|
|
||||||
const colors = require('picocolors')
|
|
||||||
const fs = require('fs-extra')
|
|
||||||
const rollup = require('rollup')
|
|
||||||
const typescript = require('@rollup/plugin-typescript')
|
|
||||||
const { nodeResolve } = require('@rollup/plugin-node-resolve')
|
|
||||||
const { Extractor, ExtractorConfig } = require('@microsoft/api-extractor')
|
|
||||||
|
|
||||||
;(async () => {
|
|
||||||
const dist = path.resolve(__dirname, '../dist')
|
|
||||||
|
|
||||||
await fs.remove(dist)
|
|
||||||
|
|
||||||
console.log()
|
|
||||||
console.log(colors.bold(colors.yellow(`Rolling up ts code...`)))
|
|
||||||
|
|
||||||
const pkg = require('../package.json')
|
|
||||||
|
|
||||||
const external = ['esbuild', ...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})]
|
|
||||||
|
|
||||||
const bundle = await rollup.rollup({
|
|
||||||
input: {
|
|
||||||
index: path.resolve(__dirname, '../src/index.ts'),
|
|
||||||
cli: path.resolve(__dirname, '../src/cli.ts')
|
|
||||||
},
|
|
||||||
external,
|
|
||||||
plugins: [
|
|
||||||
typescript({
|
|
||||||
tsconfig: path.resolve(__dirname, '../tsconfig.json')
|
|
||||||
}),
|
|
||||||
nodeResolve()
|
|
||||||
],
|
|
||||||
treeshake: {
|
|
||||||
moduleSideEffects: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await bundle.write({
|
|
||||||
dir: dist,
|
|
||||||
entryFileNames: '[name].js',
|
|
||||||
chunkFileNames: 'chunks/lib-[hash].js',
|
|
||||||
format: 'cjs'
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(colors.bold(colors.yellow(`Rolling up type definitions...`)))
|
|
||||||
|
|
||||||
if (pkg.types) {
|
|
||||||
const extractorConfig = ExtractorConfig.loadFileAndPrepare(path.resolve(__dirname, '../api-extractor.json'))
|
|
||||||
const extractorResult = Extractor.invoke(extractorConfig, {
|
|
||||||
localBuild: true,
|
|
||||||
showVerboseMessages: true
|
|
||||||
})
|
|
||||||
|
|
||||||
if (extractorResult.succeeded) {
|
|
||||||
console.log(colors.green('API Extractor completed successfully'))
|
|
||||||
} else {
|
|
||||||
console.error(
|
|
||||||
`API Extractor completed with ${extractorResult.errorCount} errors` +
|
|
||||||
` and ${extractorResult.warningCount} warnings`
|
|
||||||
)
|
|
||||||
process.exitCode = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.remove(path.resolve(dist, 'types'))
|
|
||||||
|
|
||||||
console.log(colors.green(`Build ${pkg.name}@${pkg.version} successfully`))
|
|
||||||
})()
|
|
@ -1,7 +1,7 @@
|
|||||||
// Invoked on the commit-msg git hook by simple-git-hooks.
|
// Invoked on the commit-msg git hook by simple-git-hooks.
|
||||||
|
|
||||||
const colors = require('picocolors')
|
import colors from 'picocolors'
|
||||||
const fs = require('fs')
|
import fs from 'node:fs'
|
||||||
|
|
||||||
const msgPath = process.argv[2]
|
const msgPath = process.argv[2]
|
||||||
const msg = fs.readFileSync(msgPath, 'utf-8').trim()
|
const msg = fs.readFileSync(msgPath, 'utf-8').trim()
|
||||||
@ -12,7 +12,7 @@ const commitRE =
|
|||||||
if (!commitRE.test(msg)) {
|
if (!commitRE.test(msg)) {
|
||||||
console.log()
|
console.log()
|
||||||
console.error(
|
console.error(
|
||||||
` ${colors.bgRed.white(' ERROR ')} ${colors.red(`invalid commit message format.`)}\n\n` +
|
` ${colors.bgRed(colors.white(' ERROR '))} ${colors.red(`invalid commit message format.`)}\n\n` +
|
||||||
colors.red(` Proper commit message format is required for automated changelog generation. Examples:\n\n`) +
|
colors.red(` Proper commit message format is required for automated changelog generation. Examples:\n\n`) +
|
||||||
` ${colors.green(`feat: add 'comments' option`)}\n` +
|
` ${colors.green(`feat: add 'comments' option`)}\n` +
|
||||||
` ${colors.green(`fix: handle events on blur (close #28)`)}\n\n` +
|
` ${colors.green(`fix: handle events on blur (close #28)`)}\n\n` +
|
||||||
|
10
src/build.ts
10
src/build.ts
@ -5,18 +5,28 @@ import { InlineConfig, resolveConfig } from './config'
|
|||||||
* Bundles the electron app for production.
|
* Bundles the electron app for production.
|
||||||
*/
|
*/
|
||||||
export async function build(inlineConfig: InlineConfig = {}): Promise<void> {
|
export async function build(inlineConfig: InlineConfig = {}): Promise<void> {
|
||||||
|
process.env.NODE_ENV_ELECTRON_VITE = 'production'
|
||||||
const config = await resolveConfig(inlineConfig, 'build', 'production')
|
const config = await resolveConfig(inlineConfig, 'build', 'production')
|
||||||
if (config.config) {
|
if (config.config) {
|
||||||
const mainViteConfig = config.config?.main
|
const mainViteConfig = config.config?.main
|
||||||
if (mainViteConfig) {
|
if (mainViteConfig) {
|
||||||
|
if (mainViteConfig.build?.watch) {
|
||||||
|
mainViteConfig.build.watch = null
|
||||||
|
}
|
||||||
await viteBuild(mainViteConfig)
|
await viteBuild(mainViteConfig)
|
||||||
}
|
}
|
||||||
const preloadViteConfig = config.config?.preload
|
const preloadViteConfig = config.config?.preload
|
||||||
if (preloadViteConfig) {
|
if (preloadViteConfig) {
|
||||||
|
if (preloadViteConfig.build?.watch) {
|
||||||
|
preloadViteConfig.build.watch = null
|
||||||
|
}
|
||||||
await viteBuild(preloadViteConfig)
|
await viteBuild(preloadViteConfig)
|
||||||
}
|
}
|
||||||
const rendererViteConfig = config.config?.renderer
|
const rendererViteConfig = config.config?.renderer
|
||||||
if (rendererViteConfig) {
|
if (rendererViteConfig) {
|
||||||
|
if (rendererViteConfig.build?.watch) {
|
||||||
|
rendererViteConfig.build.watch = null
|
||||||
|
}
|
||||||
await viteBuild(rendererViteConfig)
|
await viteBuild(rendererViteConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
82
src/cli.ts
82
src/cli.ts
@ -2,6 +2,7 @@ import { cac } from 'cac'
|
|||||||
import colors from 'picocolors'
|
import colors from 'picocolors'
|
||||||
import { LogLevel, createLogger } from 'vite'
|
import { LogLevel, createLogger } from 'vite'
|
||||||
import { InlineConfig } from './config'
|
import { InlineConfig } from './config'
|
||||||
|
import { version } from '../package.json'
|
||||||
|
|
||||||
const cli = cac('electron-vite')
|
const cli = cac('electron-vite')
|
||||||
|
|
||||||
@ -20,7 +21,24 @@ interface GlobalCLIOptions {
|
|||||||
m?: string
|
m?: string
|
||||||
mode?: string
|
mode?: string
|
||||||
ignoreConfigWarning?: boolean
|
ignoreConfigWarning?: boolean
|
||||||
|
sourcemap?: boolean
|
||||||
|
w?: boolean
|
||||||
|
watch?: boolean
|
||||||
outDir?: string
|
outDir?: string
|
||||||
|
entry?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DevCLIOptions {
|
||||||
|
inspect?: boolean | string
|
||||||
|
inspectBrk?: boolean | string
|
||||||
|
remoteDebuggingPort?: string
|
||||||
|
noSandbox?: boolean
|
||||||
|
rendererOnly?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PreviewCLIOptions {
|
||||||
|
noSandbox?: boolean
|
||||||
|
skipBuild?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function createInlineConfig(root: string, options: GlobalCLIOptions): InlineConfig {
|
function createInlineConfig(root: string, options: GlobalCLIOptions): InlineConfig {
|
||||||
@ -32,7 +50,9 @@ function createInlineConfig(root: string, options: GlobalCLIOptions): InlineConf
|
|||||||
clearScreen: options.clearScreen,
|
clearScreen: options.clearScreen,
|
||||||
ignoreConfigWarning: options.ignoreConfigWarning,
|
ignoreConfigWarning: options.ignoreConfigWarning,
|
||||||
build: {
|
build: {
|
||||||
outDir: options.outDir
|
sourcemap: options.sourcemap,
|
||||||
|
outDir: options.outDir,
|
||||||
|
...(options.w || options.watch ? { watch: {} } : null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,19 +65,51 @@ cli
|
|||||||
.option('-f, --filter <filter>', `[string] filter debug logs`)
|
.option('-f, --filter <filter>', `[string] filter debug logs`)
|
||||||
.option('-m, --mode <mode>', `[string] set env mode`)
|
.option('-m, --mode <mode>', `[string] set env mode`)
|
||||||
.option('--ignoreConfigWarning', `[boolean] ignore config warning`)
|
.option('--ignoreConfigWarning', `[boolean] ignore config warning`)
|
||||||
|
.option('--sourcemap', `[boolean] output source maps for debug (default: false)`)
|
||||||
.option('--outDir <dir>', `[string] output directory (default: out)`)
|
.option('--outDir <dir>', `[string] output directory (default: out)`)
|
||||||
|
.option('--entry <file>', `[string] specify electron entry file`)
|
||||||
|
|
||||||
// dev
|
// dev
|
||||||
cli
|
cli
|
||||||
.command('[root]', 'start dev server and electron app')
|
.command('[root]', 'start dev server and electron app')
|
||||||
.alias('serve')
|
.alias('serve')
|
||||||
.alias('dev')
|
.alias('dev')
|
||||||
.action(async (root: string, options: GlobalCLIOptions) => {
|
.option('-w, --watch', `[boolean] rebuilds when main process or preload script modules have changed on disk`)
|
||||||
|
.option('--inspect [port]', `[boolean | number] enable V8 inspector on the specified port`)
|
||||||
|
.option('--inspectBrk [port]', `[boolean | number] enable V8 inspector on the specified port`)
|
||||||
|
.option('--remoteDebuggingPort <port>', `[string] port for remote debugging`)
|
||||||
|
.option('--noSandbox', `[boolean] forces renderer process to run un-sandboxed`)
|
||||||
|
.option('--rendererOnly', `[boolean] only dev server for the renderer`)
|
||||||
|
.action(async (root: string, options: DevCLIOptions & GlobalCLIOptions) => {
|
||||||
|
if (options.remoteDebuggingPort) {
|
||||||
|
process.env.REMOTE_DEBUGGING_PORT = options.remoteDebuggingPort
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.inspect) {
|
||||||
|
process.env.V8_INSPECTOR_PORT = typeof options.inspect === 'number' ? `${options.inspect}` : '5858'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.inspectBrk) {
|
||||||
|
process.env.V8_INSPECTOR_BRK_PORT = typeof options.inspectBrk === 'number' ? `${options.inspectBrk}` : '5858'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.noSandbox) {
|
||||||
|
process.env.NO_SANDBOX = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options['--']) {
|
||||||
|
process.env.ELECTRON_CLI_ARGS = JSON.stringify(options['--'])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.entry) {
|
||||||
|
process.env.ELECTRON_ENTRY = options.entry
|
||||||
|
}
|
||||||
|
|
||||||
const { createServer } = await import('./server')
|
const { createServer } = await import('./server')
|
||||||
const inlineConfig = createInlineConfig(root, options)
|
const inlineConfig = createInlineConfig(root, options)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await createServer(inlineConfig)
|
await createServer(inlineConfig, { rendererOnly: options.rendererOnly })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = e as Error
|
const error = e as Error
|
||||||
createLogger(options.logLevel).error(
|
createLogger(options.logLevel).error(
|
||||||
@ -73,6 +125,10 @@ cli.command('build [root]', 'build for production').action(async (root: string,
|
|||||||
const { build } = await import('./build')
|
const { build } = await import('./build')
|
||||||
const inlineConfig = createInlineConfig(root, options)
|
const inlineConfig = createInlineConfig(root, options)
|
||||||
|
|
||||||
|
if (options.entry) {
|
||||||
|
process.env.ELECTRON_ENTRY = options.entry
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await build(inlineConfig)
|
await build(inlineConfig)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -85,12 +141,26 @@ cli.command('build [root]', 'build for production').action(async (root: string,
|
|||||||
// preview
|
// preview
|
||||||
cli
|
cli
|
||||||
.command('preview [root]', 'start electron app to preview production build')
|
.command('preview [root]', 'start electron app to preview production build')
|
||||||
.action(async (root: string, options: GlobalCLIOptions) => {
|
.option('--noSandbox', `[boolean] forces renderer process to run un-sandboxed`)
|
||||||
|
.option('--skipBuild', `[boolean] skip build`)
|
||||||
|
.action(async (root: string, options: PreviewCLIOptions & GlobalCLIOptions) => {
|
||||||
const { preview } = await import('./preview')
|
const { preview } = await import('./preview')
|
||||||
const inlineConfig = createInlineConfig(root, options)
|
const inlineConfig = createInlineConfig(root, options)
|
||||||
|
|
||||||
|
if (options.noSandbox) {
|
||||||
|
process.env.NO_SANDBOX = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.entry) {
|
||||||
|
process.env.ELECTRON_ENTRY = options.entry
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options['--']) {
|
||||||
|
process.env.ELECTRON_CLI_ARGS = JSON.stringify(options['--'])
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await preview(inlineConfig)
|
await preview(inlineConfig, { skipBuild: options.skipBuild })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = e as Error
|
const error = e as Error
|
||||||
createLogger(options.logLevel).error(colors.red(`error during preview electron app:\n${error.stack}`), { error })
|
createLogger(options.logLevel).error(colors.red(`error during preview electron app:\n${error.stack}`), { error })
|
||||||
@ -99,6 +169,6 @@ cli
|
|||||||
})
|
})
|
||||||
|
|
||||||
cli.help()
|
cli.help()
|
||||||
cli.version(require('../package.json').version)
|
cli.version(version)
|
||||||
|
|
||||||
cli.parse()
|
cli.parse()
|
||||||
|
213
src/config.ts
213
src/config.ts
@ -1,20 +1,27 @@
|
|||||||
import * as path from 'path'
|
import path from 'node:path'
|
||||||
import * as fs from 'fs'
|
import fs from 'node:fs'
|
||||||
|
import { pathToFileURL } from 'node:url'
|
||||||
|
import { createRequire } from 'node:module'
|
||||||
import colors from 'picocolors'
|
import colors from 'picocolors'
|
||||||
import {
|
import {
|
||||||
UserConfig as ViteConfig,
|
type UserConfig as ViteConfig,
|
||||||
UserConfigExport as UserViteConfigExport,
|
type UserConfigExport as ViteConfigExport,
|
||||||
ConfigEnv,
|
type ConfigEnv,
|
||||||
Plugin,
|
type Plugin,
|
||||||
LogLevel,
|
type LogLevel,
|
||||||
createLogger,
|
createLogger,
|
||||||
mergeConfig,
|
mergeConfig,
|
||||||
normalizePath
|
normalizePath
|
||||||
} from 'vite'
|
} from 'vite'
|
||||||
import { build } from 'esbuild'
|
import { build } from 'esbuild'
|
||||||
|
|
||||||
import { electronMainVitePlugin, electronPreloadVitePlugin, electronRendererVitePlugin } from './plugin'
|
import { electronMainVitePlugin, electronPreloadVitePlugin, electronRendererVitePlugin } from './plugins/electron'
|
||||||
import { isObject, dynamicImport } from './utils'
|
import assetPlugin from './plugins/asset'
|
||||||
|
import workerPlugin from './plugins/worker'
|
||||||
|
import importMetaPlugin from './plugins/importMeta'
|
||||||
|
import esmShimPlugin from './plugins/esm'
|
||||||
|
import modulePathPlugin from './plugins/modulePath'
|
||||||
|
import { isObject, isFilePathESM } from './utils'
|
||||||
|
|
||||||
export { defineConfig as defineViteConfig } from 'vite'
|
export { defineConfig as defineViteConfig } from 'vite'
|
||||||
|
|
||||||
@ -22,42 +29,42 @@ export interface UserConfig {
|
|||||||
/**
|
/**
|
||||||
* Vite config options for electron main process
|
* Vite config options for electron main process
|
||||||
*
|
*
|
||||||
* https://cn.vitejs.dev/config/
|
* https://vitejs.dev/config/
|
||||||
*/
|
*/
|
||||||
main?: ViteConfig & { configFile?: string | false }
|
main?: ViteConfig & { configFile?: string | false }
|
||||||
/**
|
/**
|
||||||
* Vite config options for electron renderer process
|
* Vite config options for electron renderer process
|
||||||
*
|
*
|
||||||
* https://cn.vitejs.dev/config/
|
* https://vitejs.dev/config/
|
||||||
*/
|
*/
|
||||||
renderer?: ViteConfig & { configFile?: string | false }
|
renderer?: ViteConfig & { configFile?: string | false }
|
||||||
/**
|
/**
|
||||||
* Vite config options for electron preload files
|
* Vite config options for electron preload files
|
||||||
*
|
*
|
||||||
* https://cn.vitejs.dev/config/
|
* https://vitejs.dev/config/
|
||||||
*/
|
*/
|
||||||
preload?: ViteConfig & { configFile?: string | false }
|
preload?: ViteConfig & { configFile?: string | false }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserConfigSchema {
|
export interface ElectronViteConfig {
|
||||||
/**
|
/**
|
||||||
* Vite config options for electron main process
|
* Vite config options for electron main process
|
||||||
*
|
*
|
||||||
* https://cn.vitejs.dev/config/
|
* https://vitejs.dev/config/
|
||||||
*/
|
*/
|
||||||
main?: UserViteConfigExport
|
main?: ViteConfigExport
|
||||||
/**
|
/**
|
||||||
* Vite config options for electron renderer process
|
* Vite config options for electron renderer process
|
||||||
*
|
*
|
||||||
* https://cn.vitejs.dev/config/
|
* https://vitejs.dev/config/
|
||||||
*/
|
*/
|
||||||
renderer?: UserViteConfigExport
|
renderer?: ViteConfigExport
|
||||||
/**
|
/**
|
||||||
* Vite config options for electron preload files
|
* Vite config options for electron preload files
|
||||||
*
|
*
|
||||||
* https://cn.vitejs.dev/config/
|
* https://vitejs.dev/config/
|
||||||
*/
|
*/
|
||||||
preload?: UserViteConfigExport
|
preload?: ViteConfigExport
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InlineConfig = Omit<ViteConfig, 'base'> & {
|
export type InlineConfig = Omit<ViteConfig, 'base'> & {
|
||||||
@ -66,13 +73,28 @@ export type InlineConfig = Omit<ViteConfig, 'base'> & {
|
|||||||
ignoreConfigWarning?: boolean
|
ignoreConfigWarning?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserConfigExport = UserConfigSchema | Promise<UserConfigSchema>
|
export type ElectronViteConfigFnObject = (env: ConfigEnv) => ElectronViteConfig
|
||||||
|
export type ElectronViteConfigFnPromise = (env: ConfigEnv) => Promise<ElectronViteConfig>
|
||||||
|
export type ElectronViteConfigFn = (env: ConfigEnv) => ElectronViteConfig | Promise<ElectronViteConfig>
|
||||||
|
|
||||||
|
export type ElectronViteConfigExport =
|
||||||
|
| ElectronViteConfig
|
||||||
|
| Promise<ElectronViteConfig>
|
||||||
|
| ElectronViteConfigFnObject
|
||||||
|
| ElectronViteConfigFnPromise
|
||||||
|
| ElectronViteConfigFn
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type helper to make it easier to use `electron.vite.config.ts`
|
* Type helper to make it easier to use `electron.vite.config.*`
|
||||||
* accepts a direct {@link UserConfig} object, or a function that returns it.
|
* accepts a direct {@link ElectronViteConfig} object, or a function that returns it.
|
||||||
|
* The function receives a object that exposes two properties:
|
||||||
|
* `command` (either `'build'` or `'serve'`), and `mode`.
|
||||||
*/
|
*/
|
||||||
export function defineConfig(config: UserConfigExport): UserConfigExport {
|
export function defineConfig(config: ElectronViteConfig): ElectronViteConfig
|
||||||
|
export function defineConfig(config: Promise<ElectronViteConfig>): Promise<ElectronViteConfig>
|
||||||
|
export function defineConfig(config: ElectronViteConfigFnObject): ElectronViteConfigFnObject
|
||||||
|
export function defineConfig(config: ElectronViteConfigExport): ElectronViteConfigExport
|
||||||
|
export function defineConfig(config: ElectronViteConfigExport): ElectronViteConfigExport {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,11 +112,7 @@ export async function resolveConfig(
|
|||||||
const config = inlineConfig
|
const config = inlineConfig
|
||||||
const mode = inlineConfig.mode || defaultMode
|
const mode = inlineConfig.mode || defaultMode
|
||||||
|
|
||||||
config.mode = mode
|
process.env.NODE_ENV = defaultMode
|
||||||
|
|
||||||
if (mode === 'production') {
|
|
||||||
process.env.NODE_ENV = 'production'
|
|
||||||
}
|
|
||||||
|
|
||||||
let userConfig: UserConfig | undefined
|
let userConfig: UserConfig | undefined
|
||||||
let configFileDependencies: string[] = []
|
let configFileDependencies: string[] = []
|
||||||
@ -122,11 +140,20 @@ export async function resolveConfig(
|
|||||||
if (loadResult.config.main) {
|
if (loadResult.config.main) {
|
||||||
const mainViteConfig: ViteConfig = mergeConfig(loadResult.config.main, deepClone(config))
|
const mainViteConfig: ViteConfig = mergeConfig(loadResult.config.main, deepClone(config))
|
||||||
|
|
||||||
|
mainViteConfig.mode = inlineConfig.mode || mainViteConfig.mode || defaultMode
|
||||||
|
|
||||||
if (outDir) {
|
if (outDir) {
|
||||||
resetOutDir(mainViteConfig, outDir, 'main')
|
resetOutDir(mainViteConfig, outDir, 'main')
|
||||||
}
|
}
|
||||||
|
|
||||||
mergePlugins(mainViteConfig, electronMainVitePlugin({ root }))
|
mergePlugins(mainViteConfig, [
|
||||||
|
...electronMainVitePlugin({ root }),
|
||||||
|
assetPlugin(),
|
||||||
|
workerPlugin(),
|
||||||
|
modulePathPlugin(),
|
||||||
|
importMetaPlugin(),
|
||||||
|
esmShimPlugin()
|
||||||
|
])
|
||||||
|
|
||||||
loadResult.config.main = mainViteConfig
|
loadResult.config.main = mainViteConfig
|
||||||
loadResult.config.main.configFile = false
|
loadResult.config.main.configFile = false
|
||||||
@ -135,10 +162,17 @@ export async function resolveConfig(
|
|||||||
if (loadResult.config.preload) {
|
if (loadResult.config.preload) {
|
||||||
const preloadViteConfig: ViteConfig = mergeConfig(loadResult.config.preload, deepClone(config))
|
const preloadViteConfig: ViteConfig = mergeConfig(loadResult.config.preload, deepClone(config))
|
||||||
|
|
||||||
|
preloadViteConfig.mode = inlineConfig.mode || preloadViteConfig.mode || defaultMode
|
||||||
|
|
||||||
if (outDir) {
|
if (outDir) {
|
||||||
resetOutDir(preloadViteConfig, outDir, 'preload')
|
resetOutDir(preloadViteConfig, outDir, 'preload')
|
||||||
}
|
}
|
||||||
mergePlugins(preloadViteConfig, electronPreloadVitePlugin({ root }))
|
mergePlugins(preloadViteConfig, [
|
||||||
|
...electronPreloadVitePlugin({ root }),
|
||||||
|
assetPlugin(),
|
||||||
|
importMetaPlugin(),
|
||||||
|
esmShimPlugin()
|
||||||
|
])
|
||||||
|
|
||||||
loadResult.config.preload = preloadViteConfig
|
loadResult.config.preload = preloadViteConfig
|
||||||
loadResult.config.preload.configFile = false
|
loadResult.config.preload.configFile = false
|
||||||
@ -147,6 +181,8 @@ export async function resolveConfig(
|
|||||||
if (loadResult.config.renderer) {
|
if (loadResult.config.renderer) {
|
||||||
const rendererViteConfig: ViteConfig = mergeConfig(loadResult.config.renderer, deepClone(config))
|
const rendererViteConfig: ViteConfig = mergeConfig(loadResult.config.renderer, deepClone(config))
|
||||||
|
|
||||||
|
rendererViteConfig.mode = inlineConfig.mode || rendererViteConfig.mode || defaultMode
|
||||||
|
|
||||||
if (outDir) {
|
if (outDir) {
|
||||||
resetOutDir(rendererViteConfig, outDir, 'renderer')
|
resetOutDir(rendererViteConfig, outDir, 'renderer')
|
||||||
}
|
}
|
||||||
@ -206,14 +242,13 @@ export async function loadConfigFromFile(
|
|||||||
config: UserConfig
|
config: UserConfig
|
||||||
dependencies: string[]
|
dependencies: string[]
|
||||||
}> {
|
}> {
|
||||||
let resolvedPath: string
|
if (configFile && /^vite.config.(js|ts|mjs|cjs|mts|cts)$/.test(configFile)) {
|
||||||
let isESM = false
|
|
||||||
|
|
||||||
if (configFile && /^vite.config.(js)|(ts)|(mjs)|(cjs)$/.test(configFile)) {
|
|
||||||
throw new Error(`config file cannot be named ${configFile}.`)
|
throw new Error(`config file cannot be named ${configFile}.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
resolvedPath = configFile ? path.resolve(configFile) : findConfigFile(configRoot, ['js', 'ts', 'mjs', 'cjs'])
|
const resolvedPath = configFile
|
||||||
|
? path.resolve(configFile)
|
||||||
|
: findConfigFile(configRoot, ['js', 'ts', 'mjs', 'cjs', 'mts', 'cts'])
|
||||||
|
|
||||||
if (!resolvedPath) {
|
if (!resolvedPath) {
|
||||||
return {
|
return {
|
||||||
@ -223,36 +258,13 @@ export async function loadConfigFromFile(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resolvedPath.endsWith('.mjs')) {
|
const isESM = isFilePathESM(resolvedPath)
|
||||||
isESM = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resolvedPath.endsWith('.js')) {
|
|
||||||
const pkg = path.join(configRoot, 'package.json')
|
|
||||||
if (fs.existsSync(pkg)) {
|
|
||||||
isESM = require(pkg).type === 'module'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const configFilePath = resolvedPath
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const bundled = await bundleConfigFile(resolvedPath)
|
const bundled = await bundleConfigFile(resolvedPath, isESM)
|
||||||
|
const userConfig = await loadConfigFormBundledFile(configRoot, resolvedPath, bundled.code, isESM)
|
||||||
|
|
||||||
if (!isESM) {
|
const config = await (typeof userConfig === 'function' ? userConfig(configEnv) : userConfig)
|
||||||
resolvedPath = path.resolve(configRoot, `${CONFIG_FILE_NAME}.mjs`)
|
|
||||||
fs.writeFileSync(resolvedPath, bundled.code)
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileUrl = require('url').pathToFileURL(resolvedPath)
|
|
||||||
|
|
||||||
const userConfig = (await dynamicImport(fileUrl)).default
|
|
||||||
|
|
||||||
if (!isESM) {
|
|
||||||
fs.unlinkSync(resolvedPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = await (typeof userConfig === 'function' ? userConfig() : userConfig)
|
|
||||||
if (!isObject(config)) {
|
if (!isObject(config)) {
|
||||||
throw new Error(`config must export or return an object`)
|
throw new Error(`config must export or return an object`)
|
||||||
}
|
}
|
||||||
@ -287,7 +299,7 @@ export async function loadConfigFromFile(
|
|||||||
if (config.preload) {
|
if (config.preload) {
|
||||||
const preloadViteConfig = config.preload
|
const preloadViteConfig = config.preload
|
||||||
preloadConfig = await (typeof preloadViteConfig === 'function' ? preloadViteConfig(configEnv) : preloadViteConfig)
|
preloadConfig = await (typeof preloadViteConfig === 'function' ? preloadViteConfig(configEnv) : preloadViteConfig)
|
||||||
if (!isObject(preloadViteConfig)) {
|
if (!isObject(preloadConfig)) {
|
||||||
throw new Error(`preload config must export or return an object`)
|
throw new Error(`preload config must export or return an object`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -299,7 +311,7 @@ export async function loadConfigFromFile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path: normalizePath(configFilePath),
|
path: normalizePath(resolvedPath),
|
||||||
config: {
|
config: {
|
||||||
main: mainConfig,
|
main: mainConfig,
|
||||||
renderer: rendererConfig,
|
renderer: rendererConfig,
|
||||||
@ -308,7 +320,7 @@ export async function loadConfigFromFile(
|
|||||||
dependencies: bundled.dependencies
|
dependencies: bundled.dependencies
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
createLogger(logLevel).error(colors.red(`failed to load config from ${configFilePath}`), { error: e as Error })
|
createLogger(logLevel).error(colors.red(`failed to load config from ${resolvedPath}`), { error: e as Error })
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,16 +335,25 @@ function findConfigFile(configRoot: string, extensions: string[]): string {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bundleConfigFile(fileName: string): Promise<{ code: string; dependencies: string[] }> {
|
async function bundleConfigFile(fileName: string, isESM: boolean): Promise<{ code: string; dependencies: string[] }> {
|
||||||
|
const dirnameVarName = '__electron_vite_injected_dirname'
|
||||||
|
const filenameVarName = '__electron_vite_injected_filename'
|
||||||
|
const importMetaUrlVarName = '__electron_vite_injected_import_meta_url'
|
||||||
const result = await build({
|
const result = await build({
|
||||||
absWorkingDir: process.cwd(),
|
absWorkingDir: process.cwd(),
|
||||||
entryPoints: [fileName],
|
entryPoints: [fileName],
|
||||||
write: false,
|
write: false,
|
||||||
|
target: ['node18'],
|
||||||
platform: 'node',
|
platform: 'node',
|
||||||
bundle: true,
|
bundle: true,
|
||||||
format: 'esm',
|
format: isESM ? 'esm' : 'cjs',
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
metafile: true,
|
metafile: true,
|
||||||
|
define: {
|
||||||
|
__dirname: dirnameVarName,
|
||||||
|
__filename: filenameVarName,
|
||||||
|
'import.meta.url': importMetaUrlVarName
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
{
|
||||||
name: 'externalize-deps',
|
name: 'externalize-deps',
|
||||||
@ -351,14 +372,16 @@ async function bundleConfigFile(fileName: string): Promise<{ code: string; depen
|
|||||||
{
|
{
|
||||||
name: 'replace-import-meta',
|
name: 'replace-import-meta',
|
||||||
setup(build): void {
|
setup(build): void {
|
||||||
build.onLoad({ filter: /\.[jt]s$/ }, async args => {
|
build.onLoad({ filter: /\.[cm]?[jt]s$/ }, async args => {
|
||||||
const contents = await fs.promises.readFile(args.path, 'utf8')
|
const contents = await fs.promises.readFile(args.path, 'utf8')
|
||||||
|
const injectValues =
|
||||||
|
`const ${dirnameVarName} = ${JSON.stringify(path.dirname(args.path))};` +
|
||||||
|
`const ${filenameVarName} = ${JSON.stringify(args.path)};` +
|
||||||
|
`const ${importMetaUrlVarName} = ${JSON.stringify(pathToFileURL(args.path).href)};`
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loader: args.path.endsWith('.ts') ? 'ts' : 'js',
|
loader: args.path.endsWith('ts') ? 'ts' : 'js',
|
||||||
contents: contents
|
contents: injectValues + contents
|
||||||
.replace(/\bimport\.meta\.url\b/g, JSON.stringify(`file://${args.path}`))
|
|
||||||
.replace(/\b__dirname\b/g, JSON.stringify(path.dirname(args.path)))
|
|
||||||
.replace(/\b__filename\b/g, JSON.stringify(args.path))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -371,3 +394,47 @@ async function bundleConfigFile(fileName: string): Promise<{ code: string; depen
|
|||||||
dependencies: result.metafile ? Object.keys(result.metafile.inputs) : []
|
dependencies: result.metafile ? Object.keys(result.metafile.inputs) : []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NodeModuleWithCompile extends NodeModule {
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
|
_compile(code: string, filename: string): any
|
||||||
|
}
|
||||||
|
|
||||||
|
const _require = createRequire(import.meta.url)
|
||||||
|
async function loadConfigFormBundledFile(
|
||||||
|
configRoot: string,
|
||||||
|
configFile: string,
|
||||||
|
bundledCode: string,
|
||||||
|
isESM: boolean
|
||||||
|
): Promise<ElectronViteConfigExport> {
|
||||||
|
if (isESM) {
|
||||||
|
const fileNameTmp = path.resolve(configRoot, `${CONFIG_FILE_NAME}.${Date.now()}.mjs`)
|
||||||
|
fs.writeFileSync(fileNameTmp, bundledCode)
|
||||||
|
|
||||||
|
const fileUrl = pathToFileURL(fileNameTmp)
|
||||||
|
try {
|
||||||
|
return (await import(fileUrl.href)).default
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(fileNameTmp)
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const extension = path.extname(configFile)
|
||||||
|
const realFileName = fs.realpathSync(configFile)
|
||||||
|
const loaderExt = extension in _require.extensions ? extension : '.js'
|
||||||
|
const defaultLoader = _require.extensions[loaderExt]!
|
||||||
|
_require.extensions[loaderExt] = (module: NodeModule, filename: string): void => {
|
||||||
|
if (filename === realFileName) {
|
||||||
|
;(module as NodeModuleWithCompile)._compile(bundledCode, filename)
|
||||||
|
} else {
|
||||||
|
defaultLoader(module, filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete _require.cache[_require.resolve(configFile)]
|
||||||
|
const raw = _require(configFile)
|
||||||
|
_require.extensions[loaderExt] = defaultLoader
|
||||||
|
return raw.__esModule ? raw.default : raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
169
src/electron.ts
Normal file
169
src/electron.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import { createRequire } from 'node:module'
|
||||||
|
import { type ChildProcess, spawn } from 'node:child_process'
|
||||||
|
import { loadPackageData } from './utils'
|
||||||
|
|
||||||
|
const _require = createRequire(import.meta.url)
|
||||||
|
|
||||||
|
const ensureElectronEntryFile = (root = process.cwd()): void => {
|
||||||
|
if (process.env.ELECTRON_ENTRY) return
|
||||||
|
const pkg = loadPackageData()
|
||||||
|
if (pkg) {
|
||||||
|
if (!pkg.main) {
|
||||||
|
throw new Error('No entry point found for electron app, please add a "main" field to package.json')
|
||||||
|
} else {
|
||||||
|
const entryPath = path.resolve(root, pkg.main)
|
||||||
|
if (!fs.existsSync(entryPath)) {
|
||||||
|
throw new Error(`No electron app entry file found: ${entryPath}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Not found: package.json')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getElectronMajorVer = (): string => {
|
||||||
|
let majorVer = process.env.ELECTRON_MAJOR_VER || ''
|
||||||
|
if (!majorVer) {
|
||||||
|
const pkg = _require.resolve('electron/package.json')
|
||||||
|
if (fs.existsSync(pkg)) {
|
||||||
|
const version = _require(pkg).version
|
||||||
|
majorVer = version.split('.')[0]
|
||||||
|
process.env.ELECTRON_MAJOR_VER = majorVer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return majorVer
|
||||||
|
}
|
||||||
|
|
||||||
|
export function supportESM(): boolean {
|
||||||
|
const majorVer = getElectronMajorVer()
|
||||||
|
return parseInt(majorVer) >= 28
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getElectronMajorVersion(): number {
|
||||||
|
const majorVer = getElectronMajorVer()
|
||||||
|
return parseInt(majorVer)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getElectronPath(): string {
|
||||||
|
let electronExecPath = process.env.ELECTRON_EXEC_PATH || ''
|
||||||
|
if (!electronExecPath) {
|
||||||
|
const electronModulePath = path.dirname(_require.resolve('electron'))
|
||||||
|
const pathFile = path.join(electronModulePath, 'path.txt')
|
||||||
|
let executablePath
|
||||||
|
if (fs.existsSync(pathFile)) {
|
||||||
|
executablePath = fs.readFileSync(pathFile, 'utf-8')
|
||||||
|
}
|
||||||
|
if (executablePath) {
|
||||||
|
electronExecPath = path.join(electronModulePath, 'dist', executablePath)
|
||||||
|
process.env.ELECTRON_EXEC_PATH = electronExecPath
|
||||||
|
} else {
|
||||||
|
throw new Error('Electron uninstall')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return electronExecPath
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getElectronNodeTarget(): string {
|
||||||
|
const electronVer = getElectronMajorVer()
|
||||||
|
|
||||||
|
const nodeVer = {
|
||||||
|
'36': '22.14',
|
||||||
|
'35': '22.14',
|
||||||
|
'34': '20.18',
|
||||||
|
'33': '20.18',
|
||||||
|
'32': '20.16',
|
||||||
|
'31': '20.14',
|
||||||
|
'30': '20.11',
|
||||||
|
'29': '20.9',
|
||||||
|
'28': '18.18',
|
||||||
|
'27': '18.17',
|
||||||
|
'26': '18.16',
|
||||||
|
'25': '18.15',
|
||||||
|
'24': '18.14',
|
||||||
|
'23': '18.12',
|
||||||
|
'22': '16.17',
|
||||||
|
'21': '16.16',
|
||||||
|
'20': '16.15',
|
||||||
|
'19': '16.14',
|
||||||
|
'18': '16.13',
|
||||||
|
'17': '16.13',
|
||||||
|
'16': '16.9',
|
||||||
|
'15': '16.5'
|
||||||
|
}
|
||||||
|
if (electronVer && parseInt(electronVer) > 10) {
|
||||||
|
let target = nodeVer[electronVer]
|
||||||
|
if (!target) target = Object.values(nodeVer).reverse()[0]
|
||||||
|
return 'node' + target
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getElectronChromeTarget(): string {
|
||||||
|
const electronVer = getElectronMajorVer()
|
||||||
|
|
||||||
|
const chromeVer = {
|
||||||
|
'36': '136',
|
||||||
|
'35': '134',
|
||||||
|
'34': '132',
|
||||||
|
'33': '130',
|
||||||
|
'32': '128',
|
||||||
|
'31': '126',
|
||||||
|
'30': '124',
|
||||||
|
'29': '122',
|
||||||
|
'28': '120',
|
||||||
|
'27': '118',
|
||||||
|
'26': '116',
|
||||||
|
'25': '114',
|
||||||
|
'24': '112',
|
||||||
|
'23': '110',
|
||||||
|
'22': '108',
|
||||||
|
'21': '106',
|
||||||
|
'20': '104',
|
||||||
|
'19': '102',
|
||||||
|
'18': '100',
|
||||||
|
'17': '98',
|
||||||
|
'16': '96',
|
||||||
|
'15': '94'
|
||||||
|
}
|
||||||
|
if (electronVer && parseInt(electronVer) > 10) {
|
||||||
|
let target = chromeVer[electronVer]
|
||||||
|
if (!target) target = Object.values(chromeVer).reverse()[0]
|
||||||
|
return 'chrome' + target
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startElectron(root: string | undefined): ChildProcess {
|
||||||
|
ensureElectronEntryFile(root)
|
||||||
|
|
||||||
|
const electronPath = getElectronPath()
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV_ELECTRON_VITE === 'development'
|
||||||
|
|
||||||
|
const args: string[] = process.env.ELECTRON_CLI_ARGS ? JSON.parse(process.env.ELECTRON_CLI_ARGS) : []
|
||||||
|
|
||||||
|
if (!!process.env.REMOTE_DEBUGGING_PORT && isDev) {
|
||||||
|
args.push(`--remote-debugging-port=${process.env.REMOTE_DEBUGGING_PORT}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!process.env.V8_INSPECTOR_PORT && isDev) {
|
||||||
|
args.push(`--inspect=${process.env.V8_INSPECTOR_PORT}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!process.env.V8_INSPECTOR_BRK_PORT && isDev) {
|
||||||
|
args.push(`--inspect-brk=${process.env.V8_INSPECTOR_BRK_PORT}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.NO_SANDBOX === '1') {
|
||||||
|
args.push('--no-sandbox')
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = process.env.ELECTRON_ENTRY || '.'
|
||||||
|
|
||||||
|
const ps = spawn(electronPath, [entry].concat(args), { stdio: 'inherit' })
|
||||||
|
ps.on('close', process.exit)
|
||||||
|
|
||||||
|
return ps
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
export { LogLevel, createLogger, splitVendorChunkPlugin } from 'vite'
|
export { type LogLevel, createLogger, mergeConfig, splitVendorChunkPlugin, splitVendorChunk } from 'vite'
|
||||||
export * from './config'
|
export * from './config'
|
||||||
export { createServer } from './server'
|
export { createServer } from './server'
|
||||||
export { build } from './build'
|
export { build } from './build'
|
||||||
export { preview } from './preview'
|
export { preview } from './preview'
|
||||||
export * from './plugin'
|
export { loadEnv } from './utils'
|
||||||
|
export * from './plugins/bytecode'
|
||||||
|
export * from './plugins/externalizeDeps'
|
||||||
|
export * from './plugins/swc'
|
||||||
|
353
src/plugin.ts
353
src/plugin.ts
@ -1,353 +0,0 @@
|
|||||||
import path from 'path'
|
|
||||||
import * as fs from 'fs'
|
|
||||||
import colors from 'picocolors'
|
|
||||||
import { builtinModules, createRequire } from 'module'
|
|
||||||
import { Plugin, mergeConfig, normalizePath } from 'vite'
|
|
||||||
|
|
||||||
export interface ElectronPluginOptions {
|
|
||||||
root?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function findLibEntry(root: string, scope: string): string {
|
|
||||||
for (const name of ['index', scope]) {
|
|
||||||
for (const ext of ['js', 'ts', 'mjs', 'cjs']) {
|
|
||||||
const entryFile = path.resolve(root, 'src', scope, `${name}.${ext}`)
|
|
||||||
if (fs.existsSync(entryFile)) {
|
|
||||||
return entryFile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function findInput(root: string, scope = 'renderer'): string {
|
|
||||||
const rendererDir = path.resolve(root, 'src', scope, 'index.html')
|
|
||||||
if (fs.existsSync(rendererDir)) {
|
|
||||||
return rendererDir
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function processEnvDefine(): Record<string, string> {
|
|
||||||
return {
|
|
||||||
'process.env': `process.env`,
|
|
||||||
'global.process.env': `global.process.env`,
|
|
||||||
'globalThis.process.env': `globalThis.process.env`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function electronMainVitePlugin(options?: ElectronPluginOptions): Plugin[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: 'vite:electron-main-preset-config',
|
|
||||||
apply: 'build',
|
|
||||||
enforce: 'pre',
|
|
||||||
config(config): void {
|
|
||||||
const root = options?.root || process.cwd()
|
|
||||||
|
|
||||||
const electornVer = getElectronMainVer(root)
|
|
||||||
const nodeTarget = getElectronNodeTarget(electornVer)
|
|
||||||
|
|
||||||
const defaultConfig = {
|
|
||||||
build: {
|
|
||||||
outDir: path.resolve(root, 'out', 'main'),
|
|
||||||
target: nodeTarget,
|
|
||||||
lib: {
|
|
||||||
entry: findLibEntry(root, 'main'),
|
|
||||||
formats: ['cjs']
|
|
||||||
},
|
|
||||||
rollupOptions: {
|
|
||||||
external: ['electron', ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
|
||||||
output: {
|
|
||||||
entryFileNames: '[name].js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
minify: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
|
||||||
config.build = buildConfig
|
|
||||||
|
|
||||||
config.define = config.define || {}
|
|
||||||
config.define = { ...processEnvDefine(), ...config.define }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vite:electron-main-resolved-config',
|
|
||||||
apply: 'build',
|
|
||||||
enforce: 'post',
|
|
||||||
configResolved(config): void {
|
|
||||||
const build = config.build
|
|
||||||
if (!build.target) {
|
|
||||||
throw new Error('build target required for the electron vite main config')
|
|
||||||
} else {
|
|
||||||
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
|
||||||
if (targets.some(t => !t.startsWith('node'))) {
|
|
||||||
throw new Error('the electron vite main config build target must be node')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const lib = build.lib
|
|
||||||
if (!lib) {
|
|
||||||
throw new Error('build lib field required for the electron vite main config')
|
|
||||||
} else {
|
|
||||||
if (!lib.entry) {
|
|
||||||
throw new Error('build entry field required for the electron vite main config')
|
|
||||||
}
|
|
||||||
if (!lib.formats) {
|
|
||||||
throw new Error('build format field required for the electron vite main config')
|
|
||||||
} else if (!lib.formats.includes('cjs')) {
|
|
||||||
throw new Error('the electron vite main config build lib format must be cjs')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function electronPreloadVitePlugin(options?: ElectronPluginOptions): Plugin[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: 'vite:electron-preload-preset-config',
|
|
||||||
apply: 'build',
|
|
||||||
enforce: 'pre',
|
|
||||||
config(config): void {
|
|
||||||
const root = options?.root || process.cwd()
|
|
||||||
|
|
||||||
const electornVer = getElectronMainVer(root)
|
|
||||||
const nodeTarget = getElectronNodeTarget(electornVer)
|
|
||||||
|
|
||||||
const defaultConfig = {
|
|
||||||
build: {
|
|
||||||
outDir: path.resolve(root, 'out', 'preload'),
|
|
||||||
target: nodeTarget,
|
|
||||||
rollupOptions: {
|
|
||||||
external: ['electron', ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
|
||||||
output: {
|
|
||||||
entryFileNames: '[name].js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
minify: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const build = config.build || {}
|
|
||||||
const rollupOptions = build.rollupOptions || {}
|
|
||||||
if (!rollupOptions.input) {
|
|
||||||
defaultConfig.build['lib'] = {
|
|
||||||
entry: findLibEntry(root, 'preload'),
|
|
||||||
formats: ['cjs']
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!rollupOptions.output) {
|
|
||||||
defaultConfig.build.rollupOptions.output['format'] = 'cjs'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
|
||||||
config.build = buildConfig
|
|
||||||
|
|
||||||
config.define = config.define || {}
|
|
||||||
config.define = { ...processEnvDefine(), ...config.define }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vite:electron-preload-resolved-config',
|
|
||||||
apply: 'build',
|
|
||||||
enforce: 'post',
|
|
||||||
configResolved(config): void {
|
|
||||||
const build = config.build
|
|
||||||
if (!build.target) {
|
|
||||||
throw new Error('build target required for the electron vite preload config')
|
|
||||||
} else {
|
|
||||||
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
|
||||||
if (targets.some(t => !t.startsWith('node'))) {
|
|
||||||
throw new Error('the electron vite preload config build target must be node')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const lib = build.lib
|
|
||||||
if (!lib) {
|
|
||||||
const rollupOptions = build.rollupOptions
|
|
||||||
if (!rollupOptions?.input) {
|
|
||||||
throw new Error('build lib field required for the electron vite preload config')
|
|
||||||
} else {
|
|
||||||
const output = rollupOptions?.output
|
|
||||||
if (output) {
|
|
||||||
const formats = Array.isArray(output) ? output : [output]
|
|
||||||
if (!formats.some(f => f !== 'cjs')) {
|
|
||||||
throw new Error('the electron vite preload config output format must be cjs')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!lib.entry) {
|
|
||||||
throw new Error('build entry field required for the electron vite preload config')
|
|
||||||
}
|
|
||||||
if (!lib.formats) {
|
|
||||||
throw new Error('build format field required for the electron vite preload config')
|
|
||||||
} else if (!lib.formats.includes('cjs')) {
|
|
||||||
throw new Error('the electron vite preload config lib format must be cjs')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function electronRendererVitePlugin(options?: ElectronPluginOptions): Plugin[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: 'vite:electron-renderer-preset-config',
|
|
||||||
enforce: 'pre',
|
|
||||||
config(config): void {
|
|
||||||
const root = options?.root || process.cwd()
|
|
||||||
|
|
||||||
config.base = config.mode === 'production' ? './' : config.base
|
|
||||||
config.root = config.root || './src/renderer'
|
|
||||||
|
|
||||||
const electornVer = getElectronMainVer(root)
|
|
||||||
const chromeTarget = getElectronChromeTarget(electornVer)
|
|
||||||
|
|
||||||
const emptyOutDir = (): boolean => {
|
|
||||||
let outDir = config.build?.outDir
|
|
||||||
if (outDir) {
|
|
||||||
if (!path.isAbsolute(outDir)) {
|
|
||||||
outDir = path.resolve(root, outDir)
|
|
||||||
}
|
|
||||||
const resolvedRoot = normalizePath(path.resolve(root))
|
|
||||||
return normalizePath(outDir).startsWith(resolvedRoot + '/')
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultConfig = {
|
|
||||||
build: {
|
|
||||||
outDir: path.resolve(root, 'out', 'renderer'),
|
|
||||||
target: chromeTarget,
|
|
||||||
polyfillModulePreload: false,
|
|
||||||
rollupOptions: {
|
|
||||||
input: findInput(root),
|
|
||||||
external: [...builtinModules.flatMap(m => [m, `node:${m}`])]
|
|
||||||
},
|
|
||||||
minify: false,
|
|
||||||
emptyOutDir: emptyOutDir()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
|
||||||
config.build = buildConfig
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vite:electron-renderer-resolved-config',
|
|
||||||
enforce: 'post',
|
|
||||||
configResolved(config): void {
|
|
||||||
if (config.base !== './' && config.base !== '/') {
|
|
||||||
config.logger.warn(colors.yellow('should not set base field for the electron vite renderer config'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const build = config.build
|
|
||||||
if (!build.target) {
|
|
||||||
throw new Error('build target required for the electron vite renderer config')
|
|
||||||
} else {
|
|
||||||
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
|
||||||
if (targets.some(t => !t.startsWith('chrome'))) {
|
|
||||||
throw new Error('the electron vite renderer config build target must be chrome')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rollupOptions = build.rollupOptions
|
|
||||||
if (!rollupOptions.input) {
|
|
||||||
config.logger.warn(colors.yellow(`index.html file is not found in ${colors.dim('/src/renderer')} directory`))
|
|
||||||
throw new Error('build rollupOptions input field required for the electron vite renderer config')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function electronConfigServeVitePlugin(options: {
|
|
||||||
configFile: string
|
|
||||||
configFileDependencies: string[]
|
|
||||||
}): Plugin {
|
|
||||||
const getShortName = (file: string, root: string): string => {
|
|
||||||
return file.startsWith(root + '/') ? path.posix.relative(root, file) : file
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'vite:electron-config-serve',
|
|
||||||
apply: 'serve',
|
|
||||||
handleHotUpdate({ file, server }): void {
|
|
||||||
const { config } = server
|
|
||||||
const logger = config.logger
|
|
||||||
const shortFile = getShortName(file, config.root)
|
|
||||||
const isConfig = file === options.configFile
|
|
||||||
const isConfigDependency = options.configFileDependencies.some(name => file === path.resolve(name))
|
|
||||||
if (isConfig || isConfigDependency) {
|
|
||||||
logger.info(`[config change] ${colors.dim(shortFile)}`)
|
|
||||||
logger.info(colors.green(`${path.relative(process.cwd(), file)} changed, restarting server...`), {
|
|
||||||
clear: true,
|
|
||||||
timestamp: true
|
|
||||||
})
|
|
||||||
try {
|
|
||||||
server.restart()
|
|
||||||
} catch (e) {
|
|
||||||
logger.error(colors.red('failed to restart server'), { error: e as Error })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElectronMainVer(root: string): string {
|
|
||||||
let mainVer = process.env.ELECTRON_MAIN_VER || ''
|
|
||||||
if (!mainVer) {
|
|
||||||
const electronModulePath = path.resolve(root, 'node_modules', 'electron')
|
|
||||||
const pkg = path.join(electronModulePath, 'package.json')
|
|
||||||
if (fs.existsSync(pkg)) {
|
|
||||||
const require = createRequire(import.meta.url)
|
|
||||||
const version = require(pkg).version
|
|
||||||
mainVer = version.split('.')[0]
|
|
||||||
process.env.ELECTRON_MAIN_VER = mainVer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mainVer
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElectronNodeTarget(electronVer: string): string {
|
|
||||||
const nodeVer = {
|
|
||||||
'19': '16.14',
|
|
||||||
'18': '16.13',
|
|
||||||
'17': '16.13',
|
|
||||||
'16': '16.9',
|
|
||||||
'15': '16.5',
|
|
||||||
'14': '14.17',
|
|
||||||
'13': '14.17',
|
|
||||||
'12': '14.16',
|
|
||||||
'11': '12.18'
|
|
||||||
}
|
|
||||||
if (electronVer && parseInt(electronVer) > 10) {
|
|
||||||
return 'node' + nodeVer[electronVer]
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElectronChromeTarget(electronVer: string): string {
|
|
||||||
const chromeVer = {
|
|
||||||
'19': '102',
|
|
||||||
'18': '100',
|
|
||||||
'17': '98',
|
|
||||||
'16': '96',
|
|
||||||
'15': '94',
|
|
||||||
'14': '93',
|
|
||||||
'13': '91',
|
|
||||||
'12': '89',
|
|
||||||
'11': '87'
|
|
||||||
}
|
|
||||||
if (electronVer && parseInt(electronVer) > 10) {
|
|
||||||
return 'chrome' + chromeVer[electronVer]
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
189
src/plugins/asset.ts
Normal file
189
src/plugins/asset.ts
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import fs from 'node:fs/promises'
|
||||||
|
import type { SourceMapInput } from 'rollup'
|
||||||
|
import { type Plugin, normalizePath } from 'vite'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
import { cleanUrl, parseRequest, getHash, toRelativePath } from '../utils'
|
||||||
|
|
||||||
|
interface AssetResolved {
|
||||||
|
type: 'asset' | 'native' | 'wasm'
|
||||||
|
file: string
|
||||||
|
query: Record<string, string> | null
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveAsset(id: string): AssetResolved | null {
|
||||||
|
const file = cleanUrl(id)
|
||||||
|
const query = parseRequest(id)
|
||||||
|
|
||||||
|
if (query && typeof query.asset === 'string') {
|
||||||
|
return {
|
||||||
|
type: 'asset',
|
||||||
|
file,
|
||||||
|
query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.endsWith('.node')) {
|
||||||
|
return {
|
||||||
|
type: 'native',
|
||||||
|
file,
|
||||||
|
query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.endsWith('.wasm?loader')) {
|
||||||
|
return {
|
||||||
|
type: 'wasm',
|
||||||
|
file,
|
||||||
|
query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeAssetRE = /__VITE_NODE_ASSET__([\w$]+)__/g
|
||||||
|
const nodePublicAssetRE = /__VITE_NODE_PUBLIC_ASSET__([a-z\d]{8})__/g
|
||||||
|
|
||||||
|
const wasmHelperId = '\0__electron-vite-wasm-helper'
|
||||||
|
|
||||||
|
const wasmHelperCode = `
|
||||||
|
import { join } from 'path'
|
||||||
|
import { readFile } from 'fs/promises'
|
||||||
|
|
||||||
|
export default async function loadWasm(file, importObject = {}) {
|
||||||
|
const wasmBuffer = await readFile(join(__dirname, file))
|
||||||
|
const result = await WebAssembly.instantiate(wasmBuffer, importObject)
|
||||||
|
return result.instance
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function assetPlugin(): Plugin {
|
||||||
|
let sourcemap: boolean | 'inline' | 'hidden' = false
|
||||||
|
let publicDir = ''
|
||||||
|
let outDir = ''
|
||||||
|
const publicAssetPathCache = new Map<string, string>()
|
||||||
|
const assetCache = new Map<string, string>()
|
||||||
|
return {
|
||||||
|
name: 'vite:node-asset',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'pre',
|
||||||
|
buildStart(): void {
|
||||||
|
publicAssetPathCache.clear()
|
||||||
|
assetCache.clear()
|
||||||
|
},
|
||||||
|
configResolved(config): void {
|
||||||
|
sourcemap = config.build.sourcemap
|
||||||
|
publicDir = normalizePath(config.publicDir)
|
||||||
|
outDir = normalizePath(path.resolve(config.root, config.build.outDir))
|
||||||
|
},
|
||||||
|
resolveId(id): string | void {
|
||||||
|
if (id === wasmHelperId) {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async load(id): Promise<string | void> {
|
||||||
|
if (id === wasmHelperId) {
|
||||||
|
return wasmHelperCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith('\0')) {
|
||||||
|
// Rollup convention, this id should be handled by the
|
||||||
|
// plugin that marked it with \0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetResolved = resolveAsset(id)
|
||||||
|
if (!assetResolved) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let referenceId: string
|
||||||
|
const file = assetResolved.file
|
||||||
|
if (publicDir && file.startsWith(publicDir)) {
|
||||||
|
const hash = getHash(file)
|
||||||
|
if (!publicAssetPathCache.get(hash)) {
|
||||||
|
publicAssetPathCache.set(hash, file)
|
||||||
|
}
|
||||||
|
referenceId = `__VITE_NODE_PUBLIC_ASSET__${hash}__`
|
||||||
|
} else {
|
||||||
|
const cached = assetCache.get(file)
|
||||||
|
if (cached) {
|
||||||
|
referenceId = cached
|
||||||
|
} else {
|
||||||
|
const source = await fs.readFile(file)
|
||||||
|
const hash = this.emitFile({
|
||||||
|
type: 'asset',
|
||||||
|
name: path.basename(file),
|
||||||
|
source: source as unknown as Uint8Array
|
||||||
|
})
|
||||||
|
referenceId = `__VITE_NODE_ASSET__${hash}__`
|
||||||
|
assetCache.set(file, referenceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assetResolved.type === 'asset') {
|
||||||
|
if (assetResolved.query && typeof assetResolved.query.asarUnpack === 'string') {
|
||||||
|
return `
|
||||||
|
import { join } from 'path'
|
||||||
|
export default join(__dirname, ${referenceId}).replace('app.asar', 'app.asar.unpacked')`
|
||||||
|
} else {
|
||||||
|
return `
|
||||||
|
import { join } from 'path'
|
||||||
|
export default join(__dirname, ${referenceId})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assetResolved.type === 'native') {
|
||||||
|
return `export default require(${referenceId})`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assetResolved.type === 'wasm') {
|
||||||
|
return `
|
||||||
|
import loadWasm from ${JSON.stringify(wasmHelperId)}
|
||||||
|
export default importObject => loadWasm(${referenceId}, importObject)`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderChunk(code, chunk): { code: string; map: SourceMapInput } | null {
|
||||||
|
let match: RegExpExecArray | null
|
||||||
|
let s: MagicString | undefined
|
||||||
|
|
||||||
|
nodeAssetRE.lastIndex = 0
|
||||||
|
if (code.match(nodeAssetRE)) {
|
||||||
|
while ((match = nodeAssetRE.exec(code))) {
|
||||||
|
s ||= new MagicString(code)
|
||||||
|
const [full, hash] = match
|
||||||
|
const filename = this.getFileName(hash)
|
||||||
|
const outputFilepath = toRelativePath(filename, chunk.fileName)
|
||||||
|
const replacement = JSON.stringify(outputFilepath)
|
||||||
|
s.overwrite(match.index, match.index + full.length, replacement, {
|
||||||
|
contentOnly: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodePublicAssetRE.lastIndex = 0
|
||||||
|
if (code.match(nodePublicAssetRE)) {
|
||||||
|
while ((match = nodePublicAssetRE.exec(code))) {
|
||||||
|
s ||= new MagicString(code)
|
||||||
|
const [full, hash] = match
|
||||||
|
const filename = publicAssetPathCache.get(hash)!
|
||||||
|
const outputFilepath = toRelativePath(filename, normalizePath(path.join(outDir, chunk.fileName)))
|
||||||
|
const replacement = JSON.stringify(outputFilepath)
|
||||||
|
s.overwrite(match.index, match.index + full.length, replacement, {
|
||||||
|
contentOnly: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s) {
|
||||||
|
return {
|
||||||
|
code: s.toString(),
|
||||||
|
map: sourcemap ? s.generateMap({ hires: 'boundary' }) : null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
379
src/plugins/bytecode.ts
Normal file
379
src/plugins/bytecode.ts
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import { spawn } from 'node:child_process'
|
||||||
|
import { createRequire } from 'node:module'
|
||||||
|
import colors from 'picocolors'
|
||||||
|
import { type Plugin, type ResolvedConfig, normalizePath, createFilter } from 'vite'
|
||||||
|
import * as babel from '@babel/core'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
import type { SourceMapInput, OutputChunk } from 'rollup'
|
||||||
|
import { getElectronPath } from '../electron'
|
||||||
|
import { toRelativePath } from '../utils'
|
||||||
|
|
||||||
|
// Inspired by https://github.com/bytenode/bytenode
|
||||||
|
|
||||||
|
const _require = createRequire(import.meta.url)
|
||||||
|
|
||||||
|
function getBytecodeCompilerPath(): string {
|
||||||
|
return path.join(path.dirname(_require.resolve('electron-vite/package.json')), 'bin', 'electron-bytecode.cjs')
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileToBytecode(code: string): Promise<Buffer> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let data = Buffer.from([])
|
||||||
|
|
||||||
|
const electronPath = getElectronPath()
|
||||||
|
const bytecodePath = getBytecodeCompilerPath()
|
||||||
|
|
||||||
|
const proc = spawn(electronPath, [bytecodePath], {
|
||||||
|
env: { ELECTRON_RUN_AS_NODE: '1' },
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe', 'ipc']
|
||||||
|
})
|
||||||
|
|
||||||
|
if (proc.stdin) {
|
||||||
|
proc.stdin.write(code)
|
||||||
|
proc.stdin.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proc.stdout) {
|
||||||
|
proc.stdout.on('data', chunk => {
|
||||||
|
data = Buffer.concat([data, chunk])
|
||||||
|
})
|
||||||
|
proc.stdout.on('error', err => {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
proc.stdout.on('end', () => {
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proc.stderr) {
|
||||||
|
proc.stderr.on('data', chunk => {
|
||||||
|
console.error('Error: ', chunk.toString())
|
||||||
|
})
|
||||||
|
proc.stderr.on('error', err => {
|
||||||
|
console.error('Error: ', err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
proc.addListener('message', message => console.log(message))
|
||||||
|
proc.addListener('error', err => console.error(err))
|
||||||
|
|
||||||
|
proc.on('error', err => reject(err))
|
||||||
|
proc.on('exit', () => {
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const bytecodeModuleLoaderCode = [
|
||||||
|
`"use strict";`,
|
||||||
|
`const fs = require("fs");`,
|
||||||
|
`const path = require("path");`,
|
||||||
|
`const vm = require("vm");`,
|
||||||
|
`const v8 = require("v8");`,
|
||||||
|
`const Module = require("module");`,
|
||||||
|
`v8.setFlagsFromString("--no-lazy");`,
|
||||||
|
`v8.setFlagsFromString("--no-flush-bytecode");`,
|
||||||
|
`const FLAG_HASH_OFFSET = 12;`,
|
||||||
|
`const SOURCE_HASH_OFFSET = 8;`,
|
||||||
|
`let dummyBytecode;`,
|
||||||
|
`function setFlagHashHeader(bytecodeBuffer) {`,
|
||||||
|
` if (!dummyBytecode) {`,
|
||||||
|
` const script = new vm.Script("", {`,
|
||||||
|
` produceCachedData: true`,
|
||||||
|
` });`,
|
||||||
|
` dummyBytecode = script.createCachedData();`,
|
||||||
|
` }`,
|
||||||
|
` dummyBytecode.slice(FLAG_HASH_OFFSET, FLAG_HASH_OFFSET + 4).copy(bytecodeBuffer, FLAG_HASH_OFFSET);`,
|
||||||
|
`};`,
|
||||||
|
`function getSourceHashHeader(bytecodeBuffer) {`,
|
||||||
|
` return bytecodeBuffer.slice(SOURCE_HASH_OFFSET, SOURCE_HASH_OFFSET + 4);`,
|
||||||
|
`};`,
|
||||||
|
`function buffer2Number(buffer) {`,
|
||||||
|
` let ret = 0;`,
|
||||||
|
` ret |= buffer[3] << 24;`,
|
||||||
|
` ret |= buffer[2] << 16;`,
|
||||||
|
` ret |= buffer[1] << 8;`,
|
||||||
|
` ret |= buffer[0];`,
|
||||||
|
` return ret;`,
|
||||||
|
`};`,
|
||||||
|
`Module._extensions[".jsc"] = Module._extensions[".cjsc"] = function (module, filename) {`,
|
||||||
|
` const bytecodeBuffer = fs.readFileSync(filename);`,
|
||||||
|
` if (!Buffer.isBuffer(bytecodeBuffer)) {`,
|
||||||
|
` throw new Error("BytecodeBuffer must be a buffer object.");`,
|
||||||
|
` }`,
|
||||||
|
` setFlagHashHeader(bytecodeBuffer);`,
|
||||||
|
` const length = buffer2Number(getSourceHashHeader(bytecodeBuffer));`,
|
||||||
|
` let dummyCode = "";`,
|
||||||
|
` if (length > 1) {`,
|
||||||
|
` dummyCode = "\\"" + "\\u200b".repeat(length - 2) + "\\"";`,
|
||||||
|
` }`,
|
||||||
|
` const script = new vm.Script(dummyCode, {`,
|
||||||
|
` filename: filename,`,
|
||||||
|
` lineOffset: 0,`,
|
||||||
|
` displayErrors: true,`,
|
||||||
|
` cachedData: bytecodeBuffer`,
|
||||||
|
` });`,
|
||||||
|
` if (script.cachedDataRejected) {`,
|
||||||
|
` throw new Error("Invalid or incompatible cached data (cachedDataRejected)");`,
|
||||||
|
` }`,
|
||||||
|
` const require = function (id) {`,
|
||||||
|
` return module.require(id);`,
|
||||||
|
` };`,
|
||||||
|
` require.resolve = function (request, options) {`,
|
||||||
|
` return Module._resolveFilename(request, module, false, options);`,
|
||||||
|
` };`,
|
||||||
|
` if (process.mainModule) {`,
|
||||||
|
` require.main = process.mainModule;`,
|
||||||
|
` }`,
|
||||||
|
` require.extensions = Module._extensions;`,
|
||||||
|
` require.cache = Module._cache;`,
|
||||||
|
` const compiledWrapper = script.runInThisContext({`,
|
||||||
|
` filename: filename,`,
|
||||||
|
` lineOffset: 0,`,
|
||||||
|
` columnOffset: 0,`,
|
||||||
|
` displayErrors: true`,
|
||||||
|
` });`,
|
||||||
|
` const dirname = path.dirname(filename);`,
|
||||||
|
` const args = [module.exports, require, module, filename, dirname, process, global];`,
|
||||||
|
` return compiledWrapper.apply(module.exports, args);`,
|
||||||
|
`};`
|
||||||
|
]
|
||||||
|
|
||||||
|
export interface BytecodeOptions {
|
||||||
|
chunkAlias?: string | string[]
|
||||||
|
transformArrowFunctions?: boolean
|
||||||
|
removeBundleJS?: boolean
|
||||||
|
protectedStrings?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile to v8 bytecode to protect source code.
|
||||||
|
*/
|
||||||
|
export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
|
||||||
|
if (process.env.NODE_ENV_ELECTRON_VITE !== 'production') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const { chunkAlias = [], transformArrowFunctions = true, removeBundleJS = true, protectedStrings = [] } = options
|
||||||
|
const _chunkAlias = Array.isArray(chunkAlias) ? chunkAlias : [chunkAlias]
|
||||||
|
|
||||||
|
const filter = createFilter(/\.(m?[jt]s|[jt]sx)$/)
|
||||||
|
|
||||||
|
const escapeRegExpString = (str: string): string => {
|
||||||
|
return str
|
||||||
|
.replace(/\\/g, '\\\\\\\\')
|
||||||
|
.replace(/[|{}()[\]^$+*?.]/g, '\\$&')
|
||||||
|
.replace(/-/g, '\\u002d')
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformAllChunks = _chunkAlias.length === 0
|
||||||
|
const isBytecodeChunk = (chunkName: string): boolean => {
|
||||||
|
return transformAllChunks || _chunkAlias.some(alias => alias === chunkName)
|
||||||
|
}
|
||||||
|
|
||||||
|
const _transform = (code: string): string => {
|
||||||
|
const re = babel.transform(code, {
|
||||||
|
plugins: ['@babel/plugin-transform-arrow-functions']
|
||||||
|
})
|
||||||
|
return re.code || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStrict = '"use strict";'
|
||||||
|
const bytecodeModuleLoader = 'bytecode-loader.cjs'
|
||||||
|
|
||||||
|
let config: ResolvedConfig
|
||||||
|
let useInRenderer = false
|
||||||
|
let bytecodeRequired = false
|
||||||
|
let bytecodeFiles: { name: string; size: number }[] = []
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'vite:bytecode',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'post',
|
||||||
|
configResolved(resolvedConfig): void {
|
||||||
|
config = resolvedConfig
|
||||||
|
useInRenderer = config.plugins.some(p => p.name === 'vite:electron-renderer-preset-config')
|
||||||
|
if (useInRenderer) {
|
||||||
|
config.logger.warn(colors.yellow('bytecodePlugin does not support renderer.'))
|
||||||
|
}
|
||||||
|
if (resolvedConfig.build.minify && protectedStrings.length > 0) {
|
||||||
|
config.logger.warn(colors.yellow('Strings cannot be protected when minification is enabled.'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transform(code, id): void | { code: string; map: SourceMapInput } {
|
||||||
|
if (config.build.minify || protectedStrings.length === 0 || !filter(id)) return
|
||||||
|
|
||||||
|
let match: RegExpExecArray | null
|
||||||
|
let s: MagicString | undefined
|
||||||
|
|
||||||
|
protectedStrings.forEach(str => {
|
||||||
|
const escapedStr = escapeRegExpString(str)
|
||||||
|
const re = new RegExp(`\\u0027${escapedStr}\\u0027|\\u0022${escapedStr}\\u0022`, 'g')
|
||||||
|
const charCodes = Array.from(str).map(s => s.charCodeAt(0))
|
||||||
|
const replacement = `String.fromCharCode(${charCodes.toString()})`
|
||||||
|
while ((match = re.exec(code))) {
|
||||||
|
s ||= new MagicString(code)
|
||||||
|
const [full] = match
|
||||||
|
s.overwrite(match.index, match.index + full.length, replacement, {
|
||||||
|
contentOnly: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (s) {
|
||||||
|
return {
|
||||||
|
code: s.toString(),
|
||||||
|
map: config.build.sourcemap ? s.generateMap({ hires: 'boundary' }) : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderChunk(code, chunk, options): { code: string } | null {
|
||||||
|
if (options.format === 'es') {
|
||||||
|
config.logger.warn(
|
||||||
|
colors.yellow(
|
||||||
|
'bytecodePlugin does not support ES module, please remove "type": "module" ' +
|
||||||
|
'in package.json or set the "build.rollupOptions.output.format" option to "cjs".'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (useInRenderer) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (chunk.type === 'chunk' && isBytecodeChunk(chunk.name)) {
|
||||||
|
bytecodeRequired = true
|
||||||
|
if (transformArrowFunctions) {
|
||||||
|
return {
|
||||||
|
code: _transform(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
generateBundle(options): void {
|
||||||
|
if (options.format !== 'es' && !useInRenderer && bytecodeRequired) {
|
||||||
|
this.emitFile({
|
||||||
|
type: 'asset',
|
||||||
|
source: bytecodeModuleLoaderCode.join('\n') + '\n',
|
||||||
|
name: 'Bytecode Loader File',
|
||||||
|
fileName: bytecodeModuleLoader
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async writeBundle(options, output): Promise<void> {
|
||||||
|
if (options.format === 'es' || useInRenderer || !bytecodeRequired) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const outDir = options.dir!
|
||||||
|
|
||||||
|
bytecodeFiles = []
|
||||||
|
|
||||||
|
const bundles = Object.keys(output)
|
||||||
|
const chunks = Object.values(output).filter(
|
||||||
|
chunk => chunk.type === 'chunk' && isBytecodeChunk(chunk.name) && chunk.fileName !== bytecodeModuleLoader
|
||||||
|
) as OutputChunk[]
|
||||||
|
const bytecodeChunks = chunks.map(chunk => chunk.fileName)
|
||||||
|
const nonEntryChunks = chunks.filter(chunk => !chunk.isEntry).map(chunk => path.basename(chunk.fileName))
|
||||||
|
|
||||||
|
const pattern = nonEntryChunks.map(chunk => `(${chunk})`).join('|')
|
||||||
|
const bytecodeRE = pattern ? new RegExp(`require\\(\\S*(?=(${pattern})\\S*\\))`, 'g') : null
|
||||||
|
|
||||||
|
const keepBundle = (chunkFileName: string): void => {
|
||||||
|
const newFileName = path.resolve(path.dirname(chunkFileName), `_${path.basename(chunkFileName)}`)
|
||||||
|
fs.renameSync(chunkFileName, newFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBytecodeLoaderBlock = (chunkFileName: string): string => {
|
||||||
|
return `require("${toRelativePath(bytecodeModuleLoader, normalizePath(chunkFileName))}");`
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
bundles.map(async name => {
|
||||||
|
const chunk = output[name]
|
||||||
|
if (chunk.type === 'chunk') {
|
||||||
|
let _code = chunk.code
|
||||||
|
if (bytecodeRE && _code.match(bytecodeRE)) {
|
||||||
|
let match: RegExpExecArray | null
|
||||||
|
const s = new MagicString(_code)
|
||||||
|
while ((match = bytecodeRE.exec(_code))) {
|
||||||
|
const [prefix, chunkName] = match
|
||||||
|
const len = prefix.length + chunkName.length
|
||||||
|
s.overwrite(match.index, match.index + len, prefix + chunkName + 'c', {
|
||||||
|
contentOnly: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_code = s.toString()
|
||||||
|
}
|
||||||
|
const chunkFileName = path.resolve(outDir, name)
|
||||||
|
if (bytecodeChunks.includes(name)) {
|
||||||
|
const bytecodeBuffer = await compileToBytecode(_code)
|
||||||
|
fs.writeFileSync(path.resolve(outDir, name + 'c'), bytecodeBuffer as unknown as Uint8Array)
|
||||||
|
if (chunk.isEntry) {
|
||||||
|
if (!removeBundleJS) {
|
||||||
|
keepBundle(chunkFileName)
|
||||||
|
}
|
||||||
|
const bytecodeLoaderBlock = getBytecodeLoaderBlock(chunk.fileName)
|
||||||
|
const bytecodeModuleBlock = `require("./${path.basename(name) + 'c'}");`
|
||||||
|
const code = `${useStrict}\n${bytecodeLoaderBlock}\n${bytecodeModuleBlock}\n`
|
||||||
|
fs.writeFileSync(chunkFileName, code)
|
||||||
|
} else {
|
||||||
|
if (removeBundleJS) {
|
||||||
|
fs.unlinkSync(chunkFileName)
|
||||||
|
} else {
|
||||||
|
keepBundle(chunkFileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bytecodeFiles.push({ name: name + 'c', size: bytecodeBuffer.length })
|
||||||
|
} else {
|
||||||
|
if (chunk.isEntry) {
|
||||||
|
let hasBytecodeMoudle = false
|
||||||
|
const idsToHandle = new Set([...chunk.imports, ...chunk.dynamicImports])
|
||||||
|
for (const moduleId of idsToHandle) {
|
||||||
|
if (bytecodeChunks.includes(moduleId)) {
|
||||||
|
hasBytecodeMoudle = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const moduleInfo = this.getModuleInfo(moduleId)
|
||||||
|
if (moduleInfo && !moduleInfo.isExternal) {
|
||||||
|
const { importers, dynamicImporters } = moduleInfo
|
||||||
|
for (const importerId of importers) idsToHandle.add(importerId)
|
||||||
|
for (const importerId of dynamicImporters) idsToHandle.add(importerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const bytecodeLoaderBlock = getBytecodeLoaderBlock(chunk.fileName)
|
||||||
|
_code = hasBytecodeMoudle
|
||||||
|
? _code.replace(/("use strict";)|('use strict';)/, `${useStrict}\n${bytecodeLoaderBlock}`)
|
||||||
|
: _code
|
||||||
|
}
|
||||||
|
fs.writeFileSync(chunkFileName, _code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
closeBundle(): void {
|
||||||
|
if (!useInRenderer) {
|
||||||
|
const chunkLimit = config.build.chunkSizeWarningLimit
|
||||||
|
const outDir = normalizePath(path.relative(config.root, path.resolve(config.root, config.build.outDir))) + '/'
|
||||||
|
config.logger.info(`${colors.green(`✓`)} ${bytecodeFiles.length} bundles compiled into bytecode.`)
|
||||||
|
let longest = 0
|
||||||
|
bytecodeFiles.forEach(file => {
|
||||||
|
const len = file.name.length
|
||||||
|
if (len > longest) longest = len
|
||||||
|
})
|
||||||
|
bytecodeFiles.forEach(file => {
|
||||||
|
const kbs = file.size / 1000
|
||||||
|
config.logger.info(
|
||||||
|
`${colors.gray(colors.white(colors.dim(outDir)))}${colors.green(file.name.padEnd(longest + 2))} ${
|
||||||
|
kbs > chunkLimit ? colors.yellow(`${kbs.toFixed(2)} kB`) : colors.dim(`${kbs.toFixed(2)} kB`)
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
bytecodeFiles = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
417
src/plugins/electron.ts
Normal file
417
src/plugins/electron.ts
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import { builtinModules } from 'node:module'
|
||||||
|
import colors from 'picocolors'
|
||||||
|
import { type Plugin, type LibraryOptions, mergeConfig, normalizePath } from 'vite'
|
||||||
|
import type { OutputOptions } from 'rollup'
|
||||||
|
import { getElectronNodeTarget, getElectronChromeTarget, supportESM } from '../electron'
|
||||||
|
import { loadPackageData } from '../utils'
|
||||||
|
|
||||||
|
export interface ElectronPluginOptions {
|
||||||
|
root?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function findLibEntry(root: string, scope: string): string | undefined {
|
||||||
|
for (const name of ['index', scope]) {
|
||||||
|
for (const ext of ['js', 'ts', 'mjs', 'cjs']) {
|
||||||
|
const entryFile = path.resolve(root, 'src', scope, `${name}.${ext}`)
|
||||||
|
if (fs.existsSync(entryFile)) {
|
||||||
|
return entryFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function findInput(root: string, scope = 'renderer'): string {
|
||||||
|
const rendererDir = path.resolve(root, 'src', scope, 'index.html')
|
||||||
|
if (fs.existsSync(rendererDir)) {
|
||||||
|
return rendererDir
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function processEnvDefine(): Record<string, string> {
|
||||||
|
return {
|
||||||
|
'process.env': `process.env`,
|
||||||
|
'global.process.env': `global.process.env`,
|
||||||
|
'globalThis.process.env': `globalThis.process.env`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveBuildOutputs(
|
||||||
|
outputs: OutputOptions | OutputOptions[] | undefined,
|
||||||
|
libOptions: LibraryOptions | false
|
||||||
|
): OutputOptions | OutputOptions[] | undefined {
|
||||||
|
if (libOptions && !Array.isArray(outputs)) {
|
||||||
|
const libFormats = libOptions.formats || []
|
||||||
|
return libFormats.map(format => ({ ...outputs, format }))
|
||||||
|
}
|
||||||
|
return outputs
|
||||||
|
}
|
||||||
|
|
||||||
|
export function electronMainVitePlugin(options?: ElectronPluginOptions): Plugin[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'vite:electron-main-preset-config',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'pre',
|
||||||
|
config(config): void {
|
||||||
|
const root = options?.root || process.cwd()
|
||||||
|
|
||||||
|
const nodeTarget = getElectronNodeTarget()
|
||||||
|
|
||||||
|
const pkg = loadPackageData() || { type: 'commonjs' }
|
||||||
|
|
||||||
|
const format = pkg.type && pkg.type === 'module' && supportESM() ? 'es' : 'cjs'
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
resolve: {
|
||||||
|
browserField: false,
|
||||||
|
mainFields: ['module', 'jsnext:main', 'jsnext'],
|
||||||
|
conditions: ['node']
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: path.resolve(root, 'out', 'main'),
|
||||||
|
target: nodeTarget,
|
||||||
|
assetsDir: 'chunks',
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['electron', /^electron\/.+/, ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
||||||
|
output: {}
|
||||||
|
},
|
||||||
|
reportCompressedSize: false,
|
||||||
|
minify: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const build = config.build || {}
|
||||||
|
const rollupOptions = build.rollupOptions || {}
|
||||||
|
if (!rollupOptions.input) {
|
||||||
|
const libOptions = build.lib
|
||||||
|
const outputOptions = rollupOptions.output
|
||||||
|
defaultConfig.build['lib'] = {
|
||||||
|
entry: findLibEntry(root, 'main'),
|
||||||
|
formats:
|
||||||
|
libOptions && libOptions.formats && libOptions.formats.length > 0
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
outputOptions && !Array.isArray(outputOptions) && outputOptions.format
|
||||||
|
? outputOptions.format
|
||||||
|
: format
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defaultConfig.build.rollupOptions.output['format'] = format
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig.build.rollupOptions.output['assetFileNames'] = path.posix.join(
|
||||||
|
build.assetsDir || defaultConfig.build.assetsDir,
|
||||||
|
'[name]-[hash].[ext]'
|
||||||
|
)
|
||||||
|
|
||||||
|
const buildConfig = mergeConfig(defaultConfig.build, build)
|
||||||
|
config.build = buildConfig
|
||||||
|
|
||||||
|
config.resolve = mergeConfig(defaultConfig.resolve, config.resolve || {})
|
||||||
|
|
||||||
|
config.define = config.define || {}
|
||||||
|
config.define = { ...processEnvDefine(), ...config.define }
|
||||||
|
|
||||||
|
config.envPrefix = config.envPrefix || ['MAIN_VITE_', 'VITE_']
|
||||||
|
|
||||||
|
config.publicDir = config.publicDir || 'resources'
|
||||||
|
// do not copy public dir
|
||||||
|
config.build.copyPublicDir = false
|
||||||
|
// module preload polyfill does not apply to nodejs (main process)
|
||||||
|
config.build.modulePreload = false
|
||||||
|
// enable ssr build
|
||||||
|
config.build.ssr = true
|
||||||
|
config.build.ssrEmitAssets = true
|
||||||
|
config.ssr = { ...config.ssr, ...{ noExternal: true } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vite:electron-main-resolved-config',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'post',
|
||||||
|
configResolved(config): void {
|
||||||
|
const build = config.build
|
||||||
|
if (!build.target) {
|
||||||
|
throw new Error('build.target option is required in the electron vite main config.')
|
||||||
|
} else {
|
||||||
|
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
||||||
|
if (targets.some(t => !t.startsWith('node'))) {
|
||||||
|
throw new Error('The electron vite main config build.target option must be "node?".')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const libOptions = build.lib
|
||||||
|
const rollupOptions = build.rollupOptions
|
||||||
|
|
||||||
|
if (!(libOptions && libOptions.entry) && !rollupOptions?.input) {
|
||||||
|
throw new Error(
|
||||||
|
'An entry point is required in the electron vite main config, ' +
|
||||||
|
'which can be specified using "build.lib.entry" or "build.rollupOptions.input".'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedOutputs = resolveBuildOutputs(rollupOptions.output, libOptions)
|
||||||
|
|
||||||
|
if (resolvedOutputs) {
|
||||||
|
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
||||||
|
if (outputs.length > 1) {
|
||||||
|
throw new Error('The electron vite main config does not support multiple outputs.')
|
||||||
|
} else {
|
||||||
|
const outpout = outputs[0]
|
||||||
|
if (['es', 'cjs'].includes(outpout.format || '')) {
|
||||||
|
if (outpout.format === 'es' && !supportESM()) {
|
||||||
|
throw new Error(
|
||||||
|
'The electron vite main config output format does not support "es", ' +
|
||||||
|
'you can upgrade electron to the latest version or switch to "cjs" format.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`The electron vite main config output format must be "cjs"${supportESM() ? ' or "es"' : ''}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function electronPreloadVitePlugin(options?: ElectronPluginOptions): Plugin[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'vite:electron-preload-preset-config',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'pre',
|
||||||
|
config(config): void {
|
||||||
|
const root = options?.root || process.cwd()
|
||||||
|
|
||||||
|
const nodeTarget = getElectronNodeTarget()
|
||||||
|
|
||||||
|
const pkg = loadPackageData() || { type: 'commonjs' }
|
||||||
|
|
||||||
|
const format = pkg.type && pkg.type === 'module' && supportESM() ? 'es' : 'cjs'
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
ssr: {
|
||||||
|
resolve: {
|
||||||
|
conditions: ['module', 'browser', 'development|production'],
|
||||||
|
mainFields: ['browser', 'module', 'jsnext:main', 'jsnext']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: path.resolve(root, 'out', 'preload'),
|
||||||
|
target: nodeTarget,
|
||||||
|
assetsDir: 'chunks',
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['electron', /^electron\/.+/, ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
||||||
|
output: {}
|
||||||
|
},
|
||||||
|
reportCompressedSize: false,
|
||||||
|
minify: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const build = config.build || {}
|
||||||
|
const rollupOptions = build.rollupOptions || {}
|
||||||
|
if (!rollupOptions.input) {
|
||||||
|
const libOptions = build.lib
|
||||||
|
const outputOptions = rollupOptions.output
|
||||||
|
defaultConfig.build['lib'] = {
|
||||||
|
entry: findLibEntry(root, 'preload'),
|
||||||
|
formats:
|
||||||
|
libOptions && libOptions.formats && libOptions.formats.length > 0
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
outputOptions && !Array.isArray(outputOptions) && outputOptions.format
|
||||||
|
? outputOptions.format
|
||||||
|
: format
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defaultConfig.build.rollupOptions.output['format'] = format
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig.build.rollupOptions.output['assetFileNames'] = path.posix.join(
|
||||||
|
build.assetsDir || defaultConfig.build.assetsDir,
|
||||||
|
'[name]-[hash].[ext]'
|
||||||
|
)
|
||||||
|
|
||||||
|
const buildConfig = mergeConfig(defaultConfig.build, build)
|
||||||
|
config.build = buildConfig
|
||||||
|
|
||||||
|
const resolvedOutputs = resolveBuildOutputs(config.build.rollupOptions!.output, config.build.lib || false)
|
||||||
|
|
||||||
|
if (resolvedOutputs) {
|
||||||
|
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
||||||
|
|
||||||
|
if (outputs.find(({ format }) => format === 'es')) {
|
||||||
|
if (Array.isArray(config.build.rollupOptions!.output)) {
|
||||||
|
config.build.rollupOptions!.output.forEach(output => {
|
||||||
|
if (output.format === 'es') {
|
||||||
|
output['entryFileNames'] = '[name].mjs'
|
||||||
|
output['chunkFileNames'] = '[name]-[hash].mjs'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
config.build.rollupOptions!.output!['entryFileNames'] = '[name].mjs'
|
||||||
|
config.build.rollupOptions!.output!['chunkFileNames'] = '[name]-[hash].mjs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.define = config.define || {}
|
||||||
|
config.define = { ...processEnvDefine(), ...config.define }
|
||||||
|
|
||||||
|
config.envPrefix = config.envPrefix || ['PRELOAD_VITE_', 'VITE_']
|
||||||
|
|
||||||
|
config.publicDir = config.publicDir || 'resources'
|
||||||
|
// do not copy public dir
|
||||||
|
config.build.copyPublicDir = false
|
||||||
|
// module preload polyfill does not apply to nodejs (preload scripts)
|
||||||
|
config.build.modulePreload = false
|
||||||
|
// enable ssr build
|
||||||
|
config.build.ssr = true
|
||||||
|
config.build.ssrEmitAssets = true
|
||||||
|
config.ssr = mergeConfig(defaultConfig.ssr, config.ssr || {})
|
||||||
|
config.ssr.noExternal = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vite:electron-preload-resolved-config',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'post',
|
||||||
|
configResolved(config): void {
|
||||||
|
const build = config.build
|
||||||
|
if (!build.target) {
|
||||||
|
throw new Error('build.target option is required in the electron vite preload config.')
|
||||||
|
} else {
|
||||||
|
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
||||||
|
if (targets.some(t => !t.startsWith('node'))) {
|
||||||
|
throw new Error('The electron vite preload config build.target must be "node?".')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const libOptions = build.lib
|
||||||
|
const rollupOptions = build.rollupOptions
|
||||||
|
|
||||||
|
if (!(libOptions && libOptions.entry) && !rollupOptions?.input) {
|
||||||
|
throw new Error(
|
||||||
|
'An entry point is required in the electron vite preload config, ' +
|
||||||
|
'which can be specified using "build.lib.entry" or "build.rollupOptions.input".'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedOutputs = resolveBuildOutputs(rollupOptions.output, libOptions)
|
||||||
|
|
||||||
|
if (resolvedOutputs) {
|
||||||
|
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
||||||
|
if (outputs.length > 1) {
|
||||||
|
throw new Error('The electron vite preload config does not support multiple outputs.')
|
||||||
|
} else {
|
||||||
|
const outpout = outputs[0]
|
||||||
|
if (['es', 'cjs'].includes(outpout.format || '')) {
|
||||||
|
if (outpout.format === 'es' && !supportESM()) {
|
||||||
|
throw new Error(
|
||||||
|
'The electron vite preload config output format does not support "es", ' +
|
||||||
|
'you can upgrade electron to the latest version or switch to "cjs" format.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`The electron vite preload config output format must be "cjs"${supportESM() ? ' or "es"' : ''}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function electronRendererVitePlugin(options?: ElectronPluginOptions): Plugin[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'vite:electron-renderer-preset-config',
|
||||||
|
enforce: 'pre',
|
||||||
|
config(config): void {
|
||||||
|
const root = options?.root || process.cwd()
|
||||||
|
|
||||||
|
config.base =
|
||||||
|
config.mode === 'production' || process.env.NODE_ENV_ELECTRON_VITE === 'production' ? './' : config.base
|
||||||
|
config.root = config.root || './src/renderer'
|
||||||
|
|
||||||
|
const chromeTarget = getElectronChromeTarget()
|
||||||
|
|
||||||
|
const emptyOutDir = (): boolean => {
|
||||||
|
let outDir = config.build?.outDir
|
||||||
|
if (outDir) {
|
||||||
|
if (!path.isAbsolute(outDir)) {
|
||||||
|
outDir = path.resolve(root, outDir)
|
||||||
|
}
|
||||||
|
const resolvedRoot = normalizePath(path.resolve(root))
|
||||||
|
return normalizePath(outDir).startsWith(resolvedRoot + '/')
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
build: {
|
||||||
|
outDir: path.resolve(root, 'out', 'renderer'),
|
||||||
|
target: chromeTarget,
|
||||||
|
modulePreload: { polyfill: false },
|
||||||
|
rollupOptions: {
|
||||||
|
input: findInput(root)
|
||||||
|
},
|
||||||
|
reportCompressedSize: false,
|
||||||
|
minify: false,
|
||||||
|
emptyOutDir: emptyOutDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.build?.outDir) {
|
||||||
|
config.build.outDir = path.resolve(root, config.build.outDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
||||||
|
config.build = buildConfig
|
||||||
|
|
||||||
|
config.envDir = config.envDir || path.resolve(root)
|
||||||
|
|
||||||
|
config.envPrefix = config.envPrefix || ['RENDERER_VITE_', 'VITE_']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vite:electron-renderer-resolved-config',
|
||||||
|
enforce: 'post',
|
||||||
|
configResolved(config): void {
|
||||||
|
if (config.base !== './' && config.base !== '/') {
|
||||||
|
config.logger.warn(colors.yellow('(!) Should not set "base" option for the electron vite renderer config.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const build = config.build
|
||||||
|
if (!build.target) {
|
||||||
|
throw new Error('build.target option is required in the electron vite renderer config.')
|
||||||
|
} else {
|
||||||
|
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
||||||
|
if (targets.some(t => !t.startsWith('chrome') && !/^es((202\d{1})|next)$/.test(t))) {
|
||||||
|
config.logger.warn(
|
||||||
|
'The electron vite renderer config build.target is not "chrome?" or "es?". This could be a mistake.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rollupOptions = build.rollupOptions
|
||||||
|
if (!rollupOptions.input) {
|
||||||
|
config.logger.warn(colors.yellow(`index.html file is not found in ${colors.dim('/src/renderer')} directory.`))
|
||||||
|
throw new Error('build.rollupOptions.input option is required in the electron vite renderer config.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
79
src/plugins/esm.ts
Normal file
79
src/plugins/esm.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* The core of this plugin was conceived by pi0 and is taken from the following repository:
|
||||||
|
* https://github.com/unjs/unbuild/blob/main/src/builder/plugins/cjs.ts
|
||||||
|
* license: https://github.com/unjs/unbuild/blob/main/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
import type { SourceMapInput } from 'rollup'
|
||||||
|
import type { Plugin } from 'vite'
|
||||||
|
|
||||||
|
import { getElectronMajorVersion } from '../electron'
|
||||||
|
|
||||||
|
const CJSyntaxRe = /__filename|__dirname|require\(|require\.resolve\(/
|
||||||
|
|
||||||
|
const CJSShim_normal = `
|
||||||
|
// -- CommonJS Shims --
|
||||||
|
import __cjs_url__ from 'node:url';
|
||||||
|
import __cjs_path__ from 'node:path';
|
||||||
|
import __cjs_mod__ from 'node:module';
|
||||||
|
const __filename = __cjs_url__.fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = __cjs_path__.dirname(__filename);
|
||||||
|
const require = __cjs_mod__.createRequire(import.meta.url);
|
||||||
|
`
|
||||||
|
|
||||||
|
const CJSShim_node_20_11 = `
|
||||||
|
// -- CommonJS Shims --
|
||||||
|
import __cjs_mod__ from 'node:module';
|
||||||
|
const __filename = import.meta.filename;
|
||||||
|
const __dirname = import.meta.dirname;
|
||||||
|
const require = __cjs_mod__.createRequire(import.meta.url);
|
||||||
|
`
|
||||||
|
|
||||||
|
const ESMStaticImportRe =
|
||||||
|
/(?<=\s|^|;)import\s*([\s"']*(?<imports>[\p{L}\p{M}\w\t\n\r $*,/{}@.]+)from\s*)?["']\s*(?<specifier>(?<="\s*)[^"]*[^\s"](?=\s*")|(?<='\s*)[^']*[^\s'](?=\s*'))\s*["'][\s;]*/gmu
|
||||||
|
|
||||||
|
interface StaticImport {
|
||||||
|
end: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function findStaticImports(code: string): StaticImport[] {
|
||||||
|
const matches: StaticImport[] = []
|
||||||
|
for (const match of code.matchAll(ESMStaticImportRe)) {
|
||||||
|
matches.push({ end: (match.index || 0) + match[0].length })
|
||||||
|
}
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function esmShimPlugin(): Plugin {
|
||||||
|
let sourcemap: boolean | 'inline' | 'hidden' = false
|
||||||
|
|
||||||
|
const CJSShim = getElectronMajorVersion() >= 30 ? CJSShim_node_20_11 : CJSShim_normal
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'vite:esm-shim',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'post',
|
||||||
|
configResolved(config): void {
|
||||||
|
sourcemap = config.build.sourcemap
|
||||||
|
},
|
||||||
|
renderChunk(code, _chunk, options): { code: string; map?: SourceMapInput } | null {
|
||||||
|
if (options.format === 'es') {
|
||||||
|
if (code.includes(CJSShim) || !CJSyntaxRe.test(code)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastESMImport = findStaticImports(code).pop()
|
||||||
|
const indexToAppend = lastESMImport ? lastESMImport.end : 0
|
||||||
|
const s = new MagicString(code)
|
||||||
|
s.appendRight(indexToAppend, CJSShim)
|
||||||
|
return {
|
||||||
|
code: s.toString(),
|
||||||
|
map: sourcemap ? s.generateMap({ hires: 'boundary' }) : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
src/plugins/externalizeDeps.ts
Normal file
43
src/plugins/externalizeDeps.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { type Plugin, mergeConfig } from 'vite'
|
||||||
|
import { loadPackageData } from '../utils'
|
||||||
|
|
||||||
|
export interface ExternalOptions {
|
||||||
|
exclude?: string[]
|
||||||
|
include?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically externalize dependencies
|
||||||
|
*/
|
||||||
|
export function externalizeDepsPlugin(options: ExternalOptions = {}): Plugin | null {
|
||||||
|
const { exclude = [], include = [] } = options
|
||||||
|
|
||||||
|
const pkg = loadPackageData() || {}
|
||||||
|
let deps = Object.keys(pkg.dependencies || {})
|
||||||
|
|
||||||
|
if (include.length) {
|
||||||
|
deps = deps.concat(include.filter(dep => dep.trim() !== ''))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exclude.length) {
|
||||||
|
deps = deps.filter(dep => !exclude.includes(dep))
|
||||||
|
}
|
||||||
|
|
||||||
|
deps = [...new Set(deps)]
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'vite:externalize-deps',
|
||||||
|
enforce: 'pre',
|
||||||
|
config(config): void {
|
||||||
|
const defaultConfig = {
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
external: deps.length > 0 ? [...deps, new RegExp(`^(${deps.join('|')})/.+`)] : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
||||||
|
config.build = buildConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
src/plugins/importMeta.ts
Normal file
21
src/plugins/importMeta.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import type { Plugin } from 'vite'
|
||||||
|
|
||||||
|
export default function importMetaPlugin(): Plugin {
|
||||||
|
return {
|
||||||
|
name: 'vite:import-meta',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'pre',
|
||||||
|
resolveImportMeta(property, { format }): string | null {
|
||||||
|
if (property === 'url' && format === 'cjs') {
|
||||||
|
return `require("url").pathToFileURL(__filename).href`
|
||||||
|
}
|
||||||
|
if (property === 'filename' && format === 'cjs') {
|
||||||
|
return `__filename`
|
||||||
|
}
|
||||||
|
if (property === 'dirname' && format === 'cjs') {
|
||||||
|
return `__dirname`
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
src/plugins/modulePath.ts
Normal file
65
src/plugins/modulePath.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import type { Plugin } from 'vite'
|
||||||
|
import type { SourceMapInput } from 'rollup'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
import { cleanUrl, parseRequest, toRelativePath } from '../utils'
|
||||||
|
|
||||||
|
const modulePathRE = /__VITE_MODULE_PATH__([\w$]+)__/g
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve `?modulePath` import and return the module bundle path.
|
||||||
|
*/
|
||||||
|
export default function modulePathPlugin(): Plugin {
|
||||||
|
let sourcemap: boolean | 'inline' | 'hidden' = false
|
||||||
|
return {
|
||||||
|
name: 'vite:module-path',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'pre',
|
||||||
|
configResolved(config): void {
|
||||||
|
sourcemap = config.build.sourcemap
|
||||||
|
},
|
||||||
|
resolveId(id, importer): string | void {
|
||||||
|
const query = parseRequest(id)
|
||||||
|
if (query && typeof query.modulePath === 'string') {
|
||||||
|
return id + `&importer=${importer}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
load(id): string | void {
|
||||||
|
const query = parseRequest(id)
|
||||||
|
if (query && typeof query.modulePath === 'string' && typeof query.importer === 'string') {
|
||||||
|
const cleanPath = cleanUrl(id)
|
||||||
|
const hash = this.emitFile({
|
||||||
|
type: 'chunk',
|
||||||
|
id: cleanPath,
|
||||||
|
importer: query.importer
|
||||||
|
})
|
||||||
|
const refId = `__VITE_MODULE_PATH__${hash}__`
|
||||||
|
return `
|
||||||
|
import { join } from 'path'
|
||||||
|
export default join(__dirname, ${refId})`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderChunk(code, chunk): { code: string; map: SourceMapInput } | null {
|
||||||
|
if (code.match(modulePathRE)) {
|
||||||
|
let match: RegExpExecArray | null
|
||||||
|
const s = new MagicString(code)
|
||||||
|
|
||||||
|
while ((match = modulePathRE.exec(code))) {
|
||||||
|
const [full, hash] = match
|
||||||
|
const filename = this.getFileName(hash)
|
||||||
|
const outputFilepath = toRelativePath(filename, chunk.fileName)
|
||||||
|
const replacement = JSON.stringify(outputFilepath)
|
||||||
|
s.overwrite(match.index, match.index + full.length, replacement, {
|
||||||
|
contentOnly: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: s.toString(),
|
||||||
|
map: sourcemap ? s.generateMap({ hires: 'boundary' }) : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
118
src/plugins/swc.ts
Normal file
118
src/plugins/swc.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { createRequire } from 'node:module'
|
||||||
|
import type { SourceMap } from 'rollup'
|
||||||
|
import type { TransformConfig, Output as TransformResult, JscConfig } from '@swc/core'
|
||||||
|
import type { Plugin, UserConfig, FilterPattern } from 'vite'
|
||||||
|
import { createFilter } from 'vite'
|
||||||
|
|
||||||
|
type SwcTransformResult = Omit<TransformResult, 'map'> & {
|
||||||
|
map: SourceMap
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwcTransformOptions = {
|
||||||
|
sourcemap?: boolean | 'inline' | undefined
|
||||||
|
minify?: boolean
|
||||||
|
} & TransformConfig
|
||||||
|
|
||||||
|
async function transformWithSWC(code: string, id: string, options: SwcTransformOptions): Promise<SwcTransformResult> {
|
||||||
|
const { sourcemap = false, minify = false } = options
|
||||||
|
|
||||||
|
delete options.sourcemap
|
||||||
|
delete options.minify
|
||||||
|
|
||||||
|
const isTs = /\.tsx?$/.test(id)
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url)
|
||||||
|
|
||||||
|
let swc: typeof import('@swc/core')
|
||||||
|
|
||||||
|
try {
|
||||||
|
swc = require('@swc/core')
|
||||||
|
} catch {
|
||||||
|
throw new Error('swc plugin require @swc/core, you need to install it.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsc: JscConfig = {
|
||||||
|
parser: {
|
||||||
|
syntax: isTs ? 'typescript' : 'ecmascript',
|
||||||
|
decorators: true
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
legacyDecorator: true,
|
||||||
|
decoratorMetadata: true,
|
||||||
|
...options
|
||||||
|
},
|
||||||
|
keepClassNames: true,
|
||||||
|
target: 'es2022',
|
||||||
|
minify: {
|
||||||
|
format: {
|
||||||
|
comments: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await swc.transform(code, {
|
||||||
|
jsc,
|
||||||
|
sourceMaps: sourcemap,
|
||||||
|
minify,
|
||||||
|
configFile: false,
|
||||||
|
swcrc: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const map: SourceMap = sourcemap && result.map ? JSON.parse(result.map) : { mappings: '' }
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: result.code,
|
||||||
|
map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SwcOptions = {
|
||||||
|
include?: FilterPattern
|
||||||
|
exclude?: FilterPattern
|
||||||
|
transformOptions?: TransformConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use SWC to support for emitting type metadata for decorators.
|
||||||
|
* When using `swcPlugin`, you need to install `@swc/core`.
|
||||||
|
*/
|
||||||
|
export function swcPlugin(options: SwcOptions = {}): Plugin {
|
||||||
|
const filter = createFilter(options.include || /\.(m?ts|[jt]sx)$/, options.exclude || /\.js$/)
|
||||||
|
let sourcemap: boolean | 'inline' = false
|
||||||
|
let minify: boolean | 'esbuild' | 'terser' = false
|
||||||
|
return {
|
||||||
|
name: 'vite:swc',
|
||||||
|
config(): UserConfig {
|
||||||
|
return {
|
||||||
|
esbuild: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async configResolved(resolvedConfig): Promise<void> {
|
||||||
|
sourcemap = resolvedConfig.build?.sourcemap === 'inline' ? 'inline' : !!resolvedConfig.build?.sourcemap
|
||||||
|
minify = resolvedConfig.build?.minify
|
||||||
|
},
|
||||||
|
async transform(code, id): Promise<void | { code: string; map: SourceMap }> {
|
||||||
|
if (filter(id)) {
|
||||||
|
const result = await transformWithSWC(code, id, { sourcemap, ...(options.transformOptions || {}) })
|
||||||
|
return {
|
||||||
|
code: result.code,
|
||||||
|
map: result.map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async renderChunk(code, chunk): Promise<null | { code: string; map: SourceMap }> {
|
||||||
|
if (!minify || minify === 'terser') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const result = await transformWithSWC(code, chunk.fileName, {
|
||||||
|
sourcemap,
|
||||||
|
minify: true,
|
||||||
|
...(options.transformOptions || {})
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
code: result.code,
|
||||||
|
map: result.map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
src/plugins/worker.ts
Normal file
65
src/plugins/worker.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import type { Plugin } from 'vite'
|
||||||
|
import type { SourceMapInput } from 'rollup'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
import { cleanUrl, parseRequest, toRelativePath } from '../utils'
|
||||||
|
|
||||||
|
const nodeWorkerAssetUrlRE = /__VITE_NODE_WORKER_ASSET__([\w$]+)__/g
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve `?nodeWorker` import and automatically generate `Worker` wrapper.
|
||||||
|
*/
|
||||||
|
export default function workerPlugin(): Plugin {
|
||||||
|
let sourcemap: boolean | 'inline' | 'hidden' = false
|
||||||
|
return {
|
||||||
|
name: 'vite:node-worker',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'pre',
|
||||||
|
configResolved(config): void {
|
||||||
|
sourcemap = config.build.sourcemap
|
||||||
|
},
|
||||||
|
resolveId(id, importer): string | void {
|
||||||
|
const query = parseRequest(id)
|
||||||
|
if (query && typeof query.nodeWorker === 'string') {
|
||||||
|
return id + `&importer=${importer}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
load(id): string | void {
|
||||||
|
const query = parseRequest(id)
|
||||||
|
if (query && typeof query.nodeWorker === 'string' && typeof query.importer === 'string') {
|
||||||
|
const cleanPath = cleanUrl(id)
|
||||||
|
const hash = this.emitFile({
|
||||||
|
type: 'chunk',
|
||||||
|
id: cleanPath,
|
||||||
|
importer: query.importer
|
||||||
|
})
|
||||||
|
const assetRefId = `__VITE_NODE_WORKER_ASSET__${hash}__`
|
||||||
|
return `
|
||||||
|
import { Worker } from 'node:worker_threads';
|
||||||
|
export default function (options) { return new Worker(new URL(${assetRefId}, import.meta.url), options); }`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderChunk(code, chunk): { code: string; map: SourceMapInput } | null {
|
||||||
|
if (code.match(nodeWorkerAssetUrlRE)) {
|
||||||
|
let match: RegExpExecArray | null
|
||||||
|
const s = new MagicString(code)
|
||||||
|
|
||||||
|
while ((match = nodeWorkerAssetUrlRE.exec(code))) {
|
||||||
|
const [full, hash] = match
|
||||||
|
const filename = this.getFileName(hash)
|
||||||
|
const outputFilepath = toRelativePath(filename, chunk.fileName)
|
||||||
|
const replacement = JSON.stringify(outputFilepath)
|
||||||
|
s.overwrite(match.index, match.index + full.length, replacement, {
|
||||||
|
contentOnly: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: s.toString(),
|
||||||
|
map: sourcemap ? s.generateMap({ hires: 'boundary' }) : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +1,17 @@
|
|||||||
import { spawn } from 'child_process'
|
|
||||||
import colors from 'picocolors'
|
import colors from 'picocolors'
|
||||||
import { createLogger } from 'vite'
|
import { createLogger } from 'vite'
|
||||||
import { InlineConfig } from './config'
|
import type { InlineConfig } from './config'
|
||||||
import { ensureElectronEntryFile, getElectronPath } from './utils'
|
import { startElectron } from './electron'
|
||||||
import { build } from './build'
|
import { build } from './build'
|
||||||
|
|
||||||
export async function preview(inlineConfig: InlineConfig = {}): Promise<void> {
|
export async function preview(inlineConfig: InlineConfig = {}, options: { skipBuild?: boolean }): Promise<void> {
|
||||||
await build(inlineConfig)
|
if (!options.skipBuild) {
|
||||||
|
await build(inlineConfig)
|
||||||
|
}
|
||||||
|
|
||||||
const logger = createLogger(inlineConfig.logLevel)
|
const logger = createLogger(inlineConfig.logLevel)
|
||||||
|
|
||||||
ensureElectronEntryFile(inlineConfig.root)
|
startElectron(inlineConfig.root)
|
||||||
|
|
||||||
const electronPath = getElectronPath()
|
logger.info(colors.green(`\nstart electron app...\n`))
|
||||||
|
|
||||||
const ps = spawn(electronPath, ['.'])
|
|
||||||
ps.stdout.on('data', chunk => {
|
|
||||||
chunk.toString().trim() && logger.info(chunk.toString())
|
|
||||||
})
|
|
||||||
ps.stderr.on('data', chunk => {
|
|
||||||
chunk.toString().trim() && logger.error(chunk.toString())
|
|
||||||
})
|
|
||||||
ps.on('close', process.exit)
|
|
||||||
|
|
||||||
logger.info(colors.green(`\nstart electron app...`))
|
|
||||||
}
|
}
|
||||||
|
126
src/server.ts
126
src/server.ts
@ -1,34 +1,86 @@
|
|||||||
import { spawn } from 'child_process'
|
import type { ChildProcess } from 'node:child_process'
|
||||||
import { createServer as ViteCreateServer, build as viteBuild, createLogger } from 'vite'
|
import {
|
||||||
|
type UserConfig as ViteConfig,
|
||||||
|
type ViteDevServer,
|
||||||
|
createServer as viteCreateServer,
|
||||||
|
build as viteBuild,
|
||||||
|
createLogger,
|
||||||
|
mergeConfig
|
||||||
|
} from 'vite'
|
||||||
import colors from 'picocolors'
|
import colors from 'picocolors'
|
||||||
import { InlineConfig, resolveConfig } from './config'
|
import { type InlineConfig, resolveConfig } from './config'
|
||||||
import { ensureElectronEntryFile, getElectronPath } from './utils'
|
import { resolveHostname } from './utils'
|
||||||
|
import { startElectron } from './electron'
|
||||||
|
|
||||||
export async function createServer(inlineConfig: InlineConfig = {}): Promise<void> {
|
export async function createServer(
|
||||||
|
inlineConfig: InlineConfig = {},
|
||||||
|
options: { rendererOnly?: boolean }
|
||||||
|
): Promise<void> {
|
||||||
|
process.env.NODE_ENV_ELECTRON_VITE = 'development'
|
||||||
const config = await resolveConfig(inlineConfig, 'serve', 'development')
|
const config = await resolveConfig(inlineConfig, 'serve', 'development')
|
||||||
if (config.config) {
|
if (config.config) {
|
||||||
const logger = createLogger(inlineConfig.logLevel)
|
const logger = createLogger(inlineConfig.logLevel)
|
||||||
|
|
||||||
|
let server: ViteDevServer | undefined
|
||||||
|
let ps: ChildProcess | undefined
|
||||||
|
|
||||||
|
const errorHook = (e): void => {
|
||||||
|
logger.error(`${colors.bgRed(colors.white(' ERROR '))} ${colors.red(e.message)}`)
|
||||||
|
}
|
||||||
|
|
||||||
const mainViteConfig = config.config?.main
|
const mainViteConfig = config.config?.main
|
||||||
if (mainViteConfig) {
|
if (mainViteConfig && !options.rendererOnly) {
|
||||||
await viteBuild(mainViteConfig)
|
const watchHook = (): void => {
|
||||||
|
logger.info(colors.green(`\nrebuild the electron main process successfully`))
|
||||||
|
|
||||||
|
if (ps) {
|
||||||
|
logger.info(colors.cyan(`\n waiting for electron to exit...`))
|
||||||
|
|
||||||
|
ps.removeAllListeners()
|
||||||
|
ps.kill()
|
||||||
|
ps = startElectron(inlineConfig.root)
|
||||||
|
|
||||||
|
logger.info(colors.green(`\nrestart electron app...`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await doBuild(mainViteConfig, watchHook, errorHook)
|
||||||
|
|
||||||
logger.info(colors.green(`\nbuild the electron main process successfully`))
|
logger.info(colors.green(`\nbuild the electron main process successfully`))
|
||||||
}
|
}
|
||||||
|
|
||||||
const preloadViteConfig = config.config?.preload
|
const preloadViteConfig = config.config?.preload
|
||||||
if (preloadViteConfig) {
|
if (preloadViteConfig && !options.rendererOnly) {
|
||||||
logger.info(colors.gray(`\n-----\n`))
|
logger.info(colors.gray(`\n-----\n`))
|
||||||
await viteBuild(preloadViteConfig)
|
|
||||||
|
const watchHook = (): void => {
|
||||||
|
logger.info(colors.green(`\nrebuild the electron preload files successfully`))
|
||||||
|
|
||||||
|
if (server) {
|
||||||
|
logger.info(colors.cyan(`\n trigger renderer reload`))
|
||||||
|
|
||||||
|
server.ws.send({ type: 'full-reload' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await doBuild(preloadViteConfig, watchHook, errorHook)
|
||||||
|
|
||||||
logger.info(colors.green(`\nbuild the electron preload files successfully`))
|
logger.info(colors.green(`\nbuild the electron preload files successfully`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.rendererOnly) {
|
||||||
|
logger.warn(
|
||||||
|
`\n${colors.yellow(colors.bold('warn'))}:${colors.yellow(
|
||||||
|
' you have skipped the main process and preload scripts building'
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const rendererViteConfig = config.config?.renderer
|
const rendererViteConfig = config.config?.renderer
|
||||||
if (rendererViteConfig) {
|
if (rendererViteConfig) {
|
||||||
logger.info(colors.gray(`\n-----\n`))
|
logger.info(colors.gray(`\n-----\n`))
|
||||||
|
|
||||||
const server = await ViteCreateServer(rendererViteConfig)
|
server = await viteCreateServer(rendererViteConfig)
|
||||||
|
|
||||||
if (!server.httpServer) {
|
if (!server.httpServer) {
|
||||||
throw new Error('HTTP server not available')
|
throw new Error('HTTP server not available')
|
||||||
@ -39,32 +91,56 @@ export async function createServer(inlineConfig: InlineConfig = {}): Promise<voi
|
|||||||
const conf = server.config.server
|
const conf = server.config.server
|
||||||
|
|
||||||
const protocol = conf.https ? 'https:' : 'http:'
|
const protocol = conf.https ? 'https:' : 'http:'
|
||||||
const host = conf.host || 'localhost'
|
const host = resolveHostname(conf.host)
|
||||||
const port = conf.port
|
const port = conf.port
|
||||||
process.env.ELECTRON_RENDERER_URL = `${protocol}//${host}:${port}`
|
process.env.ELECTRON_RENDERER_URL = `${protocol}//${host}:${port}`
|
||||||
|
|
||||||
const slogger = server.config.logger
|
const slogger = server.config.logger
|
||||||
|
|
||||||
slogger.info(colors.green(`dev server running for the electron renderer process at:\n`), {
|
slogger.info(colors.green(`dev server running for the electron renderer process at:\n`), {
|
||||||
clear: !slogger.hasWarned
|
clear: !slogger.hasWarned && !options.rendererOnly
|
||||||
})
|
})
|
||||||
|
|
||||||
server.printUrls()
|
server.printUrls()
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureElectronEntryFile(inlineConfig.root)
|
ps = startElectron(inlineConfig.root)
|
||||||
|
|
||||||
const electronPath = getElectronPath()
|
logger.info(colors.green(`\nstart electron app...\n`))
|
||||||
|
|
||||||
const ps = spawn(electronPath, ['.'])
|
|
||||||
ps.stdout.on('data', chunk => {
|
|
||||||
chunk.toString().trim() && logger.info(chunk.toString())
|
|
||||||
})
|
|
||||||
ps.stderr.on('data', chunk => {
|
|
||||||
chunk.toString().trim() && logger.error(chunk.toString())
|
|
||||||
})
|
|
||||||
ps.on('close', process.exit)
|
|
||||||
|
|
||||||
logger.info(colors.green(`\nstart electron app...`))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserConfig = ViteConfig & { configFile?: string | false }
|
||||||
|
|
||||||
|
async function doBuild(config: UserConfig, watchHook: () => void, errorHook: (e: Error) => void): Promise<void> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (config.build?.watch) {
|
||||||
|
let firstBundle = true
|
||||||
|
const closeBundle = (): void => {
|
||||||
|
if (firstBundle) {
|
||||||
|
firstBundle = false
|
||||||
|
resolve()
|
||||||
|
} else {
|
||||||
|
watchHook()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config = mergeConfig(config, {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: 'vite:electron-watcher',
|
||||||
|
closeBundle
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
viteBuild(config)
|
||||||
|
.then(() => {
|
||||||
|
if (!config.build?.watch) {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(e => errorHook(e))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
98
src/utils.ts
98
src/utils.ts
@ -1,39 +1,89 @@
|
|||||||
import * as path from 'path'
|
import { URL, URLSearchParams } from 'node:url'
|
||||||
import * as fs from 'fs'
|
import path from 'node:path'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import { createHash } from 'node:crypto'
|
||||||
|
import { createRequire } from 'node:module'
|
||||||
|
import { loadEnv as viteLoadEnv } from 'vite'
|
||||||
|
|
||||||
export function isObject(value: unknown): value is Record<string, unknown> {
|
export function isObject(value: unknown): value is Record<string, unknown> {
|
||||||
return Object.prototype.toString.call(value) === '[object Object]'
|
return Object.prototype.toString.call(value) === '[object Object]'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dynamicImport = new Function('file', 'return import(file)')
|
export const wildcardHosts = new Set(['0.0.0.0', '::', '0000:0000:0000:0000:0000:0000:0000:0000'])
|
||||||
|
|
||||||
export function ensureElectronEntryFile(root = process.cwd()): void {
|
export function resolveHostname(optionsHost: string | boolean | undefined): string {
|
||||||
|
return typeof optionsHost === 'string' && !wildcardHosts.has(optionsHost) ? optionsHost : 'localhost'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const queryRE = /\?.*$/s
|
||||||
|
export const hashRE = /#.*$/s
|
||||||
|
|
||||||
|
export const cleanUrl = (url: string): string => url.replace(hashRE, '').replace(queryRE, '')
|
||||||
|
|
||||||
|
export function parseRequest(id: string): Record<string, string> | null {
|
||||||
|
const { search } = new URL(id, 'file:')
|
||||||
|
if (!search) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return Object.fromEntries(new URLSearchParams(search))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHash(text: Buffer | string): string {
|
||||||
|
return createHash('sha256')
|
||||||
|
.update(text as unknown as Uint8Array)
|
||||||
|
.digest('hex')
|
||||||
|
.substring(0, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toRelativePath(filename: string, importer: string): string {
|
||||||
|
const relPath = path.posix.relative(path.dirname(importer), filename)
|
||||||
|
return relPath.startsWith('.') ? relPath : `./${relPath}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load `.env` files within the `envDir` (default: `process.cwd()`) .
|
||||||
|
* By default, only env variables prefixed with `VITE_`, `MAIN_VITE_`, `PRELOAD_VITE_` and
|
||||||
|
* `RENDERER_VITE_` are loaded, unless `prefixes` is changed.
|
||||||
|
*/
|
||||||
|
export function loadEnv(
|
||||||
|
mode: string,
|
||||||
|
envDir: string = process.cwd(),
|
||||||
|
prefixes: string | string[] = ['VITE_', 'MAIN_VITE_', 'PRELOAD_VITE_', 'RENDERER_VITE_']
|
||||||
|
): Record<string, string> {
|
||||||
|
return viteLoadEnv(mode, envDir, prefixes)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PackageData {
|
||||||
|
main?: string
|
||||||
|
type?: 'module' | 'commonjs'
|
||||||
|
dependencies?: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
let packageCached: PackageData | null = null
|
||||||
|
|
||||||
|
export function loadPackageData(root = process.cwd()): PackageData | null {
|
||||||
|
if (packageCached) return packageCached
|
||||||
const pkg = path.join(root, 'package.json')
|
const pkg = path.join(root, 'package.json')
|
||||||
if (fs.existsSync(pkg)) {
|
if (fs.existsSync(pkg)) {
|
||||||
const main = require(pkg).main
|
const _require = createRequire(import.meta.url)
|
||||||
if (!main) {
|
const data = _require(pkg)
|
||||||
throw new Error('not found an entry point to electorn app, please add main field for your package.json')
|
packageCached = {
|
||||||
} else {
|
main: data.main,
|
||||||
const entryPath = path.resolve(root, main)
|
type: data.type,
|
||||||
if (!fs.existsSync(entryPath)) {
|
dependencies: data.dependencies
|
||||||
throw new Error(`not found the electorn app entry file: ${entryPath}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
return packageCached
|
||||||
throw new Error('no package.json')
|
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getElectronPath(): string {
|
export function isFilePathESM(filePath: string): boolean {
|
||||||
const electronModulePath = path.resolve(process.cwd(), 'node_modules', 'electron')
|
if (/\.m[jt]s$/.test(filePath) || filePath.endsWith('.ts')) {
|
||||||
const pathFile = path.join(electronModulePath, 'path.txt')
|
return true
|
||||||
let executablePath
|
} else if (/\.c[jt]s$/.test(filePath)) {
|
||||||
if (fs.existsSync(pathFile)) {
|
return false
|
||||||
executablePath = fs.readFileSync(pathFile, 'utf-8')
|
|
||||||
}
|
|
||||||
if (executablePath) {
|
|
||||||
return path.join(electronModulePath, 'dist', executablePath)
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Electron uninstall')
|
const pkg = loadPackageData()
|
||||||
|
return pkg?.type === 'module'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,20 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2019",
|
"target": "ES2022",
|
||||||
"module": "esnext",
|
"module": "ESNext",
|
||||||
"lib": ["esnext"],
|
"lib": ["ESNext"],
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "Node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true
|
||||||
"declaration": true,
|
|
||||||
"declarationDir": "dist/types",
|
|
||||||
"outDir": "dist"
|
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src", "rollup.config.ts"]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user