mirror of
https://github.com/alex8088/electron-vite.git
synced 2025-12-15 01:10:11 +08:00
Compare commits
241 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91368b6655 | ||
|
|
b1fd596afe | ||
|
|
438e9e7672 | ||
|
|
9eba4df577 | ||
|
|
465690ab0d | ||
|
|
6aae37833e | ||
|
|
9152dfc943 | ||
|
|
0276407b5b | ||
|
|
fe7e631f47 | ||
|
|
08be346407 | ||
|
|
3e9ded666c | ||
|
|
1bba6766e8 | ||
|
|
4edffe3b9a | ||
|
|
cfd9812a91 | ||
|
|
7c7f31b2a3 | ||
|
|
ae57b2489a | ||
|
|
397b02e384 | ||
|
|
a4f7693712 | ||
|
|
56fb519092 | ||
|
|
eb0a7e3ffe | ||
|
|
de70dfe1dc | ||
|
|
8892bf3679 | ||
|
|
2576484604 | ||
|
|
88f6db2239 | ||
|
|
0a79da03db | ||
|
|
c3939ade45 | ||
|
|
38228f9b3f | ||
|
|
8b193864fd | ||
|
|
f264d41d7f | ||
|
|
5debb6f83b | ||
|
|
d530597339 | ||
|
|
a9b5326544 | ||
|
|
7587d2c674 | ||
|
|
0fb8918090 | ||
|
|
c7955aa6fd | ||
|
|
70e027d38a | ||
|
|
28bb22b353 | ||
|
|
327adc23df | ||
|
|
4a6aea3704 | ||
|
|
3f7f65bf57 | ||
|
|
0badfc493f | ||
|
|
cbb039c3e9 | ||
|
|
ad50cba495 | ||
|
|
b55eb725c4 | ||
|
|
1eeb15ea77 | ||
|
|
52abc48abf | ||
|
|
cbee52c8bb | ||
|
|
48e6f4f570 | ||
|
|
4071778f07 | ||
|
|
3fd16d0c23 | ||
|
|
d2e8b1271b | ||
|
|
28d7df6e91 | ||
|
|
b7763a7f77 | ||
|
|
1c084cc090 | ||
|
|
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 |
@ -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"
|
||||
description: Report an issue with electron-vite
|
||||
labels: ['bug', 'triage']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@ -45,7 +44,7 @@ body:
|
||||
required: true
|
||||
- label: Read the [Contributing Guidelines](https://github.com/alex8088/electron-vite/blob/master/CONTRIBUTING.md).
|
||||
required: true
|
||||
- label: Read the [docs](https://evite.netlify.app).
|
||||
- label: Read the [docs](https://electron-vite.org).
|
||||
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.
|
||||
required: true
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1 +1,5 @@
|
||||
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
|
||||
- label: Read the [Contributing Guidelines](https://github.com/alex8088/electron-vite/blob/master/CONTRIBUTING.md).
|
||||
required: true
|
||||
- label: Read the [docs](https://evite.netlify.app).
|
||||
- label: Read the [docs](https://electron-vite.org).
|
||||
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
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
dist
|
||||
|
||||
.DS_Store
|
||||
.eslintcache
|
||||
*.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"
|
||||
}
|
||||
}
|
||||
313
CHANGELOG.md
313
CHANGELOG.md
@ -1,10 +1,321 @@
|
||||
### v5.0.0 (_2025-12-07_)
|
||||
|
||||
- feat(config): add `build.externalizeDeps` and `build.bytecode` config options to replace `externalizeDepsPlugin` and `bytecodePlugin`
|
||||
- feat: reporter plugin for isolated builds
|
||||
- feat: enhanced string protection
|
||||
- feat: add `isolatedEntries` option for `preload` and `renderer` to build entries as standalone bundles [#154](https://github.com/alex8088/electron-vite/issues/154)
|
||||
- refactor(config): move the `isolateEntries` options to the `build` option
|
||||
- refactor: deprecated `externalizeDepsPlugin` and `bytecodePlugin`
|
||||
- refactor(config)!: remove function resolution for nested config fields
|
||||
- refactor(asset): remove redundant path normalization
|
||||
- refactor: split electron plugin into preset and validator plugins
|
||||
- refactor(config)!: restructure Electron Vite config interfaces
|
||||
- refactor(build): simplify build logic
|
||||
- refactor: replace `JSON.parse/stringify` with manual deep clone
|
||||
- refactor(bytecodePlugin): improved bytecode bundle generation and made a new string protection plugin
|
||||
- refactor(modulePath): better support for tree-shaking and code-splitting
|
||||
- refactor: remove Electron 18, 19, 20, 21 build compatilibity target
|
||||
- perf(buildReport): exclude node_modules from watch list
|
||||
- perf(isolateEntries): transform log
|
||||
- perf(isolateEntries): optimize entries transformation
|
||||
- perf: build compatibility target for Electron 39
|
||||
- perf(plugin): more efficient module filtering via regular expressions
|
||||
- perf(plugin): no need to cache `sourcemap` option
|
||||
- perf(plugin): lazily initialize `MagicString` and remove the redundant pre-check
|
||||
- perf(bytecodePlugin): better way to count bytecode chunks
|
||||
- perf(plugin): enhance path resolution using `import.meta.dirname` for ES modules
|
||||
- fix(modulePath): rewrite the build input instead of merging
|
||||
- fix(asset): normalize imported public asset chunk path
|
||||
- fix: avoid duplicate chunk emission
|
||||
- fix(modulePath): prevent duplicate asset emission
|
||||
- fix(modulePath): support watch mode
|
||||
- chore: fix jsdoc
|
||||
- chore: improve logging message clarity and consistency
|
||||
- chore(deps): update all non-major dependencies
|
||||
- chore: update eslint config
|
||||
- chore: replace `tseslint.config` with `defineConfig`
|
||||
- chore: remove redundant external id
|
||||
- chore: rename the file `esm` to `esmShim`
|
||||
- docs: update
|
||||
|
||||
### v5.0.0-beta.3 (_2025-11-01_)
|
||||
|
||||
See [v5.0.0-beta.3 changelog](https://github.com/alex8088/electron-vite/blob/v5.0.0-beta.3/CHANGELOG.md)
|
||||
|
||||
### v5.0.0-beta.2 (_2025-10-30_)
|
||||
|
||||
See [v5.0.0-beta.2 changelog](https://github.com/alex8088/electron-vite/blob/v5.0.0-beta.2/CHANGELOG.md)
|
||||
|
||||
### v5.0.0-beta.1 (_2025-10-29_)
|
||||
|
||||
See [v5.0.0-beta.1 changelog](https://github.com/alex8088/electron-vite/blob/v5.0.0-beta.1/CHANGELOG.md)
|
||||
|
||||
### v5.0.0-beta.0 (_2025-10-19_)
|
||||
|
||||
See [v5.0.0-beta.0 changelog](https://github.com/alex8088/electron-vite/blob/v5.0.0-beta.0/CHANGELOG.md)
|
||||
|
||||
### v4.0.1 (_2025-09-21_)
|
||||
|
||||
- perf: build compatibility target for Electron 38
|
||||
|
||||
### v4.0.0 (_2025-07-06_)
|
||||
|
||||
- refactor!: bump required node version to 20.19+, 22.12+
|
||||
- fix(deps)!: update Vite to v7 and remove cjs build
|
||||
- fix: use `import type` for type-only imports
|
||||
- perf: build compatibility target for Electron 36 ([#766](https://github.com/alex8088/electron-vite/pull/766))
|
||||
- perf: build compatibility target for Electron 37
|
||||
- chore(deps): update pnpm to v10
|
||||
- chore(deps): update all non-major dependencies
|
||||
- chore(deps): update lint-staged to v16
|
||||
|
||||
### v4.0.0-beta.0 (_2025-06-28_)
|
||||
|
||||
See [v4.0.0-beta.0 changelog](https://github.com/alex8088/electron-vite/blob/v4.0.0-beta.0/CHANGELOG.md)
|
||||
|
||||
### 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: incorrect replace `__dirname`/`__filename` in config file
|
||||
- fix: output format error under multiple entries
|
||||
|
||||
### v1.0.12 (_2022-11-02_)
|
||||
|
||||
124
README.md
124
README.md
@ -9,17 +9,17 @@
|
||||
|
||||
<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/wx-vue-next?color=blue" alt="license" />
|
||||
<img src="https://img.shields.io/github/license/alex8088/electron-vite?color=blue" alt="license" />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://evite.netlify.app/">Documentation</a> |
|
||||
<a href="https://evite.netlify.app/guide/">Getting Started</a> |
|
||||
<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-evite.netlify.app/">中文文档</a>
|
||||
<a href="https://cn.electron-vite.org">中文文档</a>
|
||||
</p>
|
||||
|
||||
<br />
|
||||
@ -27,16 +27,15 @@
|
||||
|
||||
## Features
|
||||
|
||||
- ⚡️ Inherit all the benefits of Vite and use the same way as [Vite](https://vitejs.dev).
|
||||
- 📦The main process, renderers and preload scripts are all built with Vite.
|
||||
- 🛠The main process, renderers and preload scripts Vite configuration combined into one file.
|
||||
- 💡Pre-configured for Electron, don't worry about configuration.
|
||||
- 🚀HMR for renderer processes.
|
||||
- 🔥The main process and preload scripts support hot reloading.
|
||||
- 🔌Easy to debug.
|
||||
- 🔒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.
|
||||
- ⚡️ [Vite](https://vitejs.dev) powered and use the same way.
|
||||
- 🛠 Pre-configure with sensible defaults optimized for Electron.
|
||||
- 💡 Optimize asset handling for Electron main process.
|
||||
- 🚀 Fast HMR & hot reloading.
|
||||
- 🔥 Isolated build for multi-entry application development.
|
||||
- ✨ Simplify multi-threading development.
|
||||
- 🔒 Compile code to v8 bytecode to protect source code.
|
||||
- 🔌 Easy to debug in IDEs such as VSCode or WebStorm.
|
||||
- 📦 Out-of-the-box support for TypeScript, Vue, React, Svelte, SolidJS and more.
|
||||
|
||||
## Usage
|
||||
|
||||
@ -60,7 +59,7 @@ In a project where `electron-vite` is installed, you can use `electron-vite` bin
|
||||
}
|
||||
```
|
||||
|
||||
### Configuring
|
||||
### Configuration
|
||||
|
||||
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:
|
||||
|
||||
@ -79,97 +78,24 @@ export default {
|
||||
}
|
||||
```
|
||||
|
||||
### Use HMR in Renderer
|
||||
|
||||
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 local URL.
|
||||
|
||||
```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'))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Hot Reloading
|
||||
|
||||
Hot reloading refers to quickly rebuilding and restarting the Electron app when the main process or preload scripts module changes. In fact, it's not really hot reloading, but similar. It also brings a good development experience to developers.
|
||||
|
||||
There are two ways to enable it:
|
||||
|
||||
1. Use CLI option `-w` or `--watch`, e.g. `electron-vite dev --watch`. This is the preferred way, it's more flexible.
|
||||
|
||||
2. Use configuration option `build.watch` and set to `{}`. In addition, more watcher options can be configured, see [WatcherOptions](https://rollupjs.org/guide/en/#watch-options).
|
||||
|
||||
### Debugging in VSCode
|
||||
|
||||
Add a file `.vscode/launch.json` with the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Main Process",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
|
||||
},
|
||||
"runtimeArgs": ["--sourcemap"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Then, set some breakpoints in `main.ts` (source code), and start debugging in the `VSCode Debug View`.
|
||||
|
||||
### Source Code Protection
|
||||
|
||||
Use the plugin `bytecodePlugin` to enable it:
|
||||
|
||||
```js
|
||||
import { defineConfig, bytecodePlugin } from 'electron-vite'
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [bytecodePlugin()]
|
||||
},
|
||||
preload: {
|
||||
plugins: [bytecodePlugin()]
|
||||
},
|
||||
renderer: {
|
||||
// ...
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
`bytecodePlugin` only works in production and supports the main process and preload scripts.
|
||||
|
||||
Also, you can learn more by playing with the [example](https://github.com/alex8088/electron-vite-bytecode-example).
|
||||
|
||||
### Getting 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
|
||||
npm create @quick-start/electron@latest
|
||||
```
|
||||
|
||||
Currently supported template presets include:
|
||||
|
||||
| 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) |
|
||||
|
||||
## Contribution
|
||||
|
||||
See [Contributing Guide](CONTRIBUTING.md).
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,7 +24,7 @@ if (debugIndex > 0) {
|
||||
}
|
||||
|
||||
function run() {
|
||||
require('../dist/cli')
|
||||
import('../dist/cli.js')
|
||||
}
|
||||
|
||||
run()
|
||||
|
||||
69
eslint.config.js
Normal file
69
eslint.config.js
Normal file
@ -0,0 +1,69 @@
|
||||
// ts-check
|
||||
import { defineConfig } from 'eslint/config'
|
||||
import eslint from '@eslint/js'
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||
import globals from 'globals'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default defineConfig(
|
||||
{ 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
|
||||
}
|
||||
},
|
||||
settings: {
|
||||
node: {
|
||||
version: '^20.19.0 || >=22.12.0'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'prettier/prettier': 'warn',
|
||||
'no-empty': ['warn', { allowEmptyCatch: true }],
|
||||
'@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
|
||||
}
|
||||
],
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
{ prefer: 'type-imports', disallowTypeAnnotations: false }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['*.js', '*.mjs'],
|
||||
rules: {
|
||||
'@typescript-eslint/explicit-function-return-type': 'off'
|
||||
}
|
||||
}
|
||||
)
|
||||
109
node.d.ts
vendored
109
node.d.ts
vendored
@ -1,5 +1,112 @@
|
||||
// node worker
|
||||
declare module '*?nodeWorker' {
|
||||
import { Worker, WorkerOptions } from 'node:worker_threads'
|
||||
import type { 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
|
||||
}
|
||||
|
||||
81
package.json
81
package.json
@ -1,9 +1,17 @@
|
||||
{
|
||||
"name": "electron-vite",
|
||||
"version": "1.0.13",
|
||||
"version": "5.0.0",
|
||||
"description": "Electron build tooling based on Vite",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./node": {
|
||||
"types": "./node.d.ts"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"bin": {
|
||||
"electron-vite": "bin/electron-vite.js"
|
||||
},
|
||||
@ -13,8 +21,9 @@
|
||||
"node.d.ts"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.12.4",
|
||||
"author": "Alex Wei<https://github.com/alex8088>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@ -24,7 +33,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/alex8088/electron-vite/issues"
|
||||
},
|
||||
"homepage": "https://github.com/alex8088/electron-vite#readme",
|
||||
"homepage": "https://electron-vite.org",
|
||||
"keywords": [
|
||||
"electron",
|
||||
"vite",
|
||||
@ -33,9 +42,9 @@
|
||||
],
|
||||
"scripts": {
|
||||
"format": "prettier --write .",
|
||||
"lint": "eslint --ext .ts src/**",
|
||||
"lint": "eslint --cache .",
|
||||
"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": {
|
||||
"pre-commit": "npx lint-staged",
|
||||
@ -52,7 +61,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/core": "^1.0.0",
|
||||
"vite": "^3.0.0"
|
||||
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/core": {
|
||||
@ -60,31 +69,41 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "^7.33.5",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@rollup/plugin-typescript": "^9.0.2",
|
||||
"@swc/core": "^1.3.11",
|
||||
"@types/node": "16.18.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.42.0",
|
||||
"@typescript-eslint/parser": "^5.42.0",
|
||||
"eslint": "^8.26.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"fs-extra": "^10.1.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"prettier": "^2.7.1",
|
||||
"rollup": "^2.79.1",
|
||||
"simple-git-hooks": "^2.8.1",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.8.4",
|
||||
"vite": "^3.2.2"
|
||||
"@eslint/js": "^9.37.0",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"@rollup/plugin-typescript": "^12.1.4",
|
||||
"@swc/core": "^1.13.5",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/node": "^22.18.11",
|
||||
"eslint": "^9.37.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"globals": "^16.4.0",
|
||||
"lint-staged": "^16.2.4",
|
||||
"prettier": "^3.6.2",
|
||||
"rollup": "^4.52.4",
|
||||
"rollup-plugin-dts": "^6.2.3",
|
||||
"rollup-plugin-rm": "^1.0.2",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.46.1",
|
||||
"vite": "^7.1.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.19.6",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.18.6",
|
||||
"@babel/core": "^7.28.4",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.27.1",
|
||||
"cac": "^6.7.14",
|
||||
"esbuild": "^0.15.12",
|
||||
"magic-string": "^0.26.7",
|
||||
"picocolors": "^1.0.0"
|
||||
"esbuild": "^0.25.11",
|
||||
"magic-string": "^0.30.19",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@swc/core",
|
||||
"esbuild",
|
||||
"simple-git-hooks"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
4215
pnpm-lock.yaml
generated
4215
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
41
rollup.config.ts
Normal file
41
rollup.config.ts
Normal file
@ -0,0 +1,41 @@
|
||||
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 = [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})]
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
input: ['src/index.ts', 'src/cli.ts'],
|
||||
output: [
|
||||
{
|
||||
dir: 'dist',
|
||||
entryFileNames: '[name].js',
|
||||
chunkFileNames: 'chunks/lib-[hash].js',
|
||||
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,13 +1,13 @@
|
||||
// Invoked on the commit-msg git hook by simple-git-hooks.
|
||||
|
||||
const colors = require('picocolors')
|
||||
const fs = require('fs')
|
||||
import colors from 'picocolors'
|
||||
import fs from 'node:fs'
|
||||
|
||||
const msgPath = process.argv[2]
|
||||
const msg = fs.readFileSync(msgPath, 'utf-8').trim()
|
||||
|
||||
const commitRE =
|
||||
/^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?: .{1,50}/
|
||||
/^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?!?: .{1,50}/
|
||||
|
||||
if (!commitRE.test(msg)) {
|
||||
console.log()
|
||||
|
||||
37
src/build.ts
37
src/build.ts
@ -1,5 +1,5 @@
|
||||
import { build as viteBuild } from 'vite'
|
||||
import { InlineConfig, resolveConfig } from './config'
|
||||
import { type InlineConfig, resolveConfig } from './config'
|
||||
|
||||
/**
|
||||
* Bundles the electron app for production.
|
||||
@ -7,27 +7,22 @@ import { InlineConfig, resolveConfig } from './config'
|
||||
export async function build(inlineConfig: InlineConfig = {}): Promise<void> {
|
||||
process.env.NODE_ENV_ELECTRON_VITE = 'production'
|
||||
const config = await resolveConfig(inlineConfig, 'build', 'production')
|
||||
if (config.config) {
|
||||
const mainViteConfig = config.config?.main
|
||||
if (mainViteConfig) {
|
||||
if (mainViteConfig.build?.watch) {
|
||||
mainViteConfig.build.watch = null
|
||||
|
||||
if (!config.config) {
|
||||
return
|
||||
}
|
||||
|
||||
// Build targets in order: main -> preload -> renderer
|
||||
const buildTargets = ['main', 'preload', 'renderer'] as const
|
||||
|
||||
for (const target of buildTargets) {
|
||||
const viteConfig = config.config[target]
|
||||
if (viteConfig) {
|
||||
// Disable watch mode in production builds
|
||||
if (viteConfig.build?.watch) {
|
||||
viteConfig.build.watch = null
|
||||
}
|
||||
await viteBuild(mainViteConfig)
|
||||
}
|
||||
const preloadViteConfig = config.config?.preload
|
||||
if (preloadViteConfig) {
|
||||
if (preloadViteConfig.build?.watch) {
|
||||
preloadViteConfig.build.watch = null
|
||||
}
|
||||
await viteBuild(preloadViteConfig)
|
||||
}
|
||||
const rendererViteConfig = config.config?.renderer
|
||||
if (rendererViteConfig) {
|
||||
if (rendererViteConfig.build?.watch) {
|
||||
rendererViteConfig.build.watch = null
|
||||
}
|
||||
await viteBuild(rendererViteConfig)
|
||||
await viteBuild(viteConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
74
src/cli.ts
74
src/cli.ts
@ -1,7 +1,8 @@
|
||||
import { cac } from 'cac'
|
||||
import colors from 'picocolors'
|
||||
import { LogLevel, createLogger } from 'vite'
|
||||
import { InlineConfig } from './config'
|
||||
import { type LogLevel, createLogger } from 'vite'
|
||||
import type { InlineConfig } from './config'
|
||||
import { version } from '../package.json'
|
||||
|
||||
const cli = cac('electron-vite')
|
||||
|
||||
@ -24,6 +25,20 @@ interface GlobalCLIOptions {
|
||||
w?: boolean
|
||||
watch?: boolean
|
||||
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 {
|
||||
@ -52,6 +67,7 @@ cli
|
||||
.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('--entry <file>', `[string] specify electron entry file`)
|
||||
|
||||
// dev
|
||||
cli
|
||||
@ -59,12 +75,41 @@ cli
|
||||
.alias('serve')
|
||||
.alias('dev')
|
||||
.option('-w, --watch', `[boolean] rebuilds when main process or preload script modules have changed on disk`)
|
||||
.action(async (root: string, options: GlobalCLIOptions) => {
|
||||
.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 inlineConfig = createInlineConfig(root, options)
|
||||
|
||||
try {
|
||||
await createServer(inlineConfig)
|
||||
await createServer(inlineConfig, { rendererOnly: options.rendererOnly })
|
||||
} catch (e) {
|
||||
const error = e as Error
|
||||
createLogger(options.logLevel).error(
|
||||
@ -80,6 +125,10 @@ cli.command('build [root]', 'build for production').action(async (root: string,
|
||||
const { build } = await import('./build')
|
||||
const inlineConfig = createInlineConfig(root, options)
|
||||
|
||||
if (options.entry) {
|
||||
process.env.ELECTRON_ENTRY = options.entry
|
||||
}
|
||||
|
||||
try {
|
||||
await build(inlineConfig)
|
||||
} catch (e) {
|
||||
@ -92,11 +141,24 @@ cli.command('build [root]', 'build for production').action(async (root: string,
|
||||
// preview
|
||||
cli
|
||||
.command('preview [root]', 'start electron app to preview production build')
|
||||
.option('--noSandbox', `[boolean] forces renderer process to run un-sandboxed`)
|
||||
.option('--skipBuild', `[boolean] skip build`)
|
||||
.action(async (root: string, options: { skipBuild?: boolean } & GlobalCLIOptions) => {
|
||||
.action(async (root: string, options: PreviewCLIOptions & GlobalCLIOptions) => {
|
||||
const { preview } = await import('./preview')
|
||||
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 {
|
||||
await preview(inlineConfig, { skipBuild: options.skipBuild })
|
||||
} catch (e) {
|
||||
@ -107,6 +169,6 @@ cli
|
||||
})
|
||||
|
||||
cli.help()
|
||||
cli.version(require('../package.json').version)
|
||||
cli.version(version)
|
||||
|
||||
cli.parse()
|
||||
|
||||
361
src/config.ts
361
src/config.ts
@ -5,9 +5,10 @@ import { createRequire } from 'node:module'
|
||||
import colors from 'picocolors'
|
||||
import {
|
||||
type UserConfig as ViteConfig,
|
||||
type UserConfigExport as UserViteConfigExport,
|
||||
type ConfigEnv,
|
||||
type PluginOption,
|
||||
type Plugin,
|
||||
type BuildEnvironmentOptions as ViteBuildOptions,
|
||||
type LogLevel,
|
||||
createLogger,
|
||||
mergeConfig,
|
||||
@ -15,52 +16,130 @@ import {
|
||||
} from 'vite'
|
||||
import { build } from 'esbuild'
|
||||
|
||||
import { electronMainVitePlugin, electronPreloadVitePlugin, electronRendererVitePlugin } from './plugins/electron'
|
||||
import {
|
||||
electronMainConfigPresetPlugin,
|
||||
electronMainConfigValidatorPlugin,
|
||||
electronPreloadConfigPresetPlugin,
|
||||
electronPreloadConfigValidatorPlugin,
|
||||
electronRendererConfigPresetPlugin,
|
||||
electronRendererConfigValidatorPlugin
|
||||
} from './plugins/electron'
|
||||
import assetPlugin from './plugins/asset'
|
||||
import workerPlugin from './plugins/worker'
|
||||
import { isObject, dynamicImport } from './utils'
|
||||
import importMetaPlugin from './plugins/importMeta'
|
||||
import esmShimPlugin from './plugins/esmShim'
|
||||
import modulePathPlugin from './plugins/modulePath'
|
||||
import isolateEntriesPlugin from './plugins/isolateEntries'
|
||||
import { type ExternalOptions, externalizeDepsPlugin } from './plugins/externalizeDeps'
|
||||
import { type BytecodeOptions, bytecodePlugin } from './plugins/bytecode'
|
||||
import { isObject, isFilePathESM, deepClone, asyncFlatten } from './utils'
|
||||
|
||||
export { defineConfig as defineViteConfig } from 'vite'
|
||||
|
||||
interface IsolatedEntriesMixin {
|
||||
/**
|
||||
* Build each entry point as an isolated bundle without code splitting.
|
||||
*
|
||||
* When enabled, each entry will include all its dependencies inline,
|
||||
* preventing automatic code splitting across entries and ensuring each
|
||||
* output file is fully standalone.
|
||||
*
|
||||
* **Important**: When using `isolatedEntries` in `preload` config, you
|
||||
* should also disable `build.externalizeDeps` to ensure third-party dependencies
|
||||
* from `node_modules` are bundled together, which is required for Electron
|
||||
* sandbox support.
|
||||
*
|
||||
* @experimental
|
||||
* @default false
|
||||
*/
|
||||
isolatedEntries?: boolean
|
||||
}
|
||||
|
||||
interface ExternalizeDepsMixin {
|
||||
/**
|
||||
* Options pass on to `externalizeDeps` plugin in electron-vite.
|
||||
*
|
||||
* Automatically externalize dependencies.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
externalizeDeps?: boolean | ExternalOptions
|
||||
}
|
||||
|
||||
interface BytecodeMixin {
|
||||
/**
|
||||
* Options pass on to `bytecode` plugin in electron-vite.
|
||||
* https://electron-vite.org/guide/source-code-protection#options
|
||||
*
|
||||
* Compile source code to v8 bytecode.
|
||||
*/
|
||||
bytecode?: boolean | BytecodeOptions
|
||||
}
|
||||
|
||||
interface MainBuildOptions extends ViteBuildOptions, ExternalizeDepsMixin, BytecodeMixin {}
|
||||
|
||||
interface PreloadBuildOptions extends ViteBuildOptions, ExternalizeDepsMixin, BytecodeMixin, IsolatedEntriesMixin {}
|
||||
|
||||
interface RendererBuildOptions extends ViteBuildOptions, IsolatedEntriesMixin {}
|
||||
|
||||
interface BaseViteConfig<T> extends Omit<ViteConfig, 'build'> {
|
||||
/**
|
||||
* Build specific options
|
||||
*/
|
||||
build?: T
|
||||
}
|
||||
|
||||
export interface MainViteConfig extends BaseViteConfig<MainBuildOptions> {}
|
||||
|
||||
export interface PreloadViteConfig extends BaseViteConfig<PreloadBuildOptions> {}
|
||||
|
||||
export interface RendererViteConfig extends BaseViteConfig<RendererBuildOptions> {}
|
||||
|
||||
export interface UserConfig {
|
||||
/**
|
||||
* Vite config options for electron main process
|
||||
*
|
||||
* https://cn.vitejs.dev/config/
|
||||
* @see https://vitejs.dev/config/
|
||||
*/
|
||||
main?: ViteConfig & { configFile?: string | false }
|
||||
main?: MainViteConfig
|
||||
/**
|
||||
* Vite config options for electron renderer process
|
||||
*
|
||||
* https://cn.vitejs.dev/config/
|
||||
* @see https://vitejs.dev/config/
|
||||
*/
|
||||
renderer?: ViteConfig & { configFile?: string | false }
|
||||
renderer?: RendererViteConfig
|
||||
/**
|
||||
* Vite config options for electron preload files
|
||||
* Vite config options for electron preload scripts
|
||||
*
|
||||
* https://cn.vitejs.dev/config/
|
||||
* @see https://vitejs.dev/config/
|
||||
*/
|
||||
preload?: ViteConfig & { configFile?: string | false }
|
||||
preload?: PreloadViteConfig
|
||||
}
|
||||
|
||||
export interface UserConfigSchema {
|
||||
/**
|
||||
* Vite config options for electron main process
|
||||
*
|
||||
* https://cn.vitejs.dev/config/
|
||||
*/
|
||||
main?: UserViteConfigExport
|
||||
/**
|
||||
* Vite config options for electron renderer process
|
||||
*
|
||||
* https://cn.vitejs.dev/config/
|
||||
*/
|
||||
renderer?: UserViteConfigExport
|
||||
/**
|
||||
* Vite config options for electron preload files
|
||||
*
|
||||
* https://cn.vitejs.dev/config/
|
||||
*/
|
||||
preload?: UserViteConfigExport
|
||||
export type ElectronViteConfigFnObject = (env: ConfigEnv) => UserConfig
|
||||
export type ElectronViteConfigFnPromise = (env: ConfigEnv) => Promise<UserConfig>
|
||||
export type ElectronViteConfigFn = (env: ConfigEnv) => UserConfig | Promise<UserConfig>
|
||||
|
||||
export type ElectronViteConfigExport =
|
||||
| UserConfig
|
||||
| Promise<UserConfig>
|
||||
| ElectronViteConfigFnObject
|
||||
| ElectronViteConfigFnPromise
|
||||
| ElectronViteConfigFn
|
||||
|
||||
/**
|
||||
* Type helper to make it easier to use `electron.vite.config.*`
|
||||
* accepts a direct {@link UserConfig} 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: UserConfig): UserConfig
|
||||
export function defineConfig(config: Promise<UserConfig>): Promise<UserConfig>
|
||||
export function defineConfig(config: ElectronViteConfigFnObject): ElectronViteConfigFnObject
|
||||
export function defineConfig(config: ElectronViteConfigFnPromise): ElectronViteConfigFnPromise
|
||||
export function defineConfig(config: ElectronViteConfigExport): ElectronViteConfigExport
|
||||
export function defineConfig(config: ElectronViteConfigExport): ElectronViteConfigExport {
|
||||
return config
|
||||
}
|
||||
|
||||
export type InlineConfig = Omit<ViteConfig, 'base'> & {
|
||||
@ -69,17 +148,6 @@ export type InlineConfig = Omit<ViteConfig, 'base'> & {
|
||||
ignoreConfigWarning?: boolean
|
||||
}
|
||||
|
||||
export type UserConfigFn = () => UserConfigSchema | Promise<UserConfigSchema>
|
||||
export type UserConfigExport = UserConfigSchema | Promise<UserConfigSchema> | UserConfigFn
|
||||
|
||||
/**
|
||||
* Type helper to make it easier to use `electron.vite.config.ts`
|
||||
* accepts a direct {@link UserConfig} object, or a function that returns it.
|
||||
*/
|
||||
export function defineConfig(config: UserConfigExport): UserConfigExport {
|
||||
return config
|
||||
}
|
||||
|
||||
export interface ResolvedConfig {
|
||||
config?: UserConfig
|
||||
configFile?: string
|
||||
@ -94,11 +162,7 @@ export async function resolveConfig(
|
||||
const config = inlineConfig
|
||||
const mode = inlineConfig.mode || defaultMode
|
||||
|
||||
config.mode = mode
|
||||
|
||||
if (mode === 'production') {
|
||||
process.env.NODE_ENV = 'production'
|
||||
}
|
||||
process.env.NODE_ENV = defaultMode
|
||||
|
||||
let userConfig: UserConfig | undefined
|
||||
let configFileDependencies: string[] = []
|
||||
@ -109,6 +173,7 @@ export async function resolveConfig(
|
||||
mode,
|
||||
command
|
||||
}
|
||||
|
||||
const loadResult = await loadConfigFromFile(
|
||||
configEnv,
|
||||
configFile,
|
||||
@ -116,49 +181,130 @@ export async function resolveConfig(
|
||||
config.logLevel,
|
||||
config.ignoreConfigWarning
|
||||
)
|
||||
|
||||
if (loadResult) {
|
||||
const root = config.root
|
||||
delete config.root
|
||||
delete config.configFile
|
||||
|
||||
config.configFile = false
|
||||
|
||||
const outDir = config.build?.outDir
|
||||
|
||||
if (loadResult.config.main) {
|
||||
const mainViteConfig: ViteConfig = mergeConfig(loadResult.config.main, deepClone(config))
|
||||
const mainViteConfig: MainViteConfig = mergeConfig(loadResult.config.main, deepClone(config))
|
||||
|
||||
mainViteConfig.mode = inlineConfig.mode || mainViteConfig.mode || defaultMode
|
||||
|
||||
if (outDir) {
|
||||
resetOutDir(mainViteConfig, outDir, 'main')
|
||||
}
|
||||
|
||||
mergePlugins(mainViteConfig, [...electronMainVitePlugin({ root }), workerPlugin()])
|
||||
const configDrivenPlugins: PluginOption[] = await resolveConfigDrivenPlugins(mainViteConfig)
|
||||
|
||||
const builtInMainPlugins: PluginOption[] = [
|
||||
electronMainConfigPresetPlugin({ root }),
|
||||
electronMainConfigValidatorPlugin(),
|
||||
assetPlugin(),
|
||||
workerPlugin(),
|
||||
modulePathPlugin(
|
||||
mergeConfig(
|
||||
{
|
||||
plugins: [
|
||||
electronMainConfigPresetPlugin({ root }),
|
||||
assetPlugin(),
|
||||
importMetaPlugin(),
|
||||
esmShimPlugin(),
|
||||
...configDrivenPlugins
|
||||
]
|
||||
},
|
||||
mainViteConfig
|
||||
)
|
||||
),
|
||||
importMetaPlugin(),
|
||||
esmShimPlugin(),
|
||||
...configDrivenPlugins
|
||||
]
|
||||
|
||||
mainViteConfig.plugins = builtInMainPlugins.concat(mainViteConfig.plugins || [])
|
||||
|
||||
loadResult.config.main = mainViteConfig
|
||||
loadResult.config.main.configFile = false
|
||||
}
|
||||
|
||||
if (loadResult.config.preload) {
|
||||
const preloadViteConfig: ViteConfig = mergeConfig(loadResult.config.preload, deepClone(config))
|
||||
const preloadViteConfig: PreloadViteConfig = mergeConfig(loadResult.config.preload, deepClone(config))
|
||||
|
||||
preloadViteConfig.mode = inlineConfig.mode || preloadViteConfig.mode || defaultMode
|
||||
|
||||
if (outDir) {
|
||||
resetOutDir(preloadViteConfig, outDir, 'preload')
|
||||
}
|
||||
mergePlugins(preloadViteConfig, electronPreloadVitePlugin({ root }))
|
||||
|
||||
const configDrivenPlugins: PluginOption[] = await resolveConfigDrivenPlugins(preloadViteConfig)
|
||||
|
||||
const builtInPreloadPlugins: PluginOption[] = [
|
||||
electronPreloadConfigPresetPlugin({ root }),
|
||||
electronPreloadConfigValidatorPlugin(),
|
||||
assetPlugin(),
|
||||
importMetaPlugin(),
|
||||
esmShimPlugin(),
|
||||
...configDrivenPlugins
|
||||
]
|
||||
|
||||
if (preloadViteConfig.build?.isolatedEntries) {
|
||||
builtInPreloadPlugins.push(
|
||||
isolateEntriesPlugin(
|
||||
mergeConfig(
|
||||
{
|
||||
plugins: [
|
||||
electronPreloadConfigPresetPlugin({ root }),
|
||||
assetPlugin(),
|
||||
importMetaPlugin(),
|
||||
esmShimPlugin(),
|
||||
...configDrivenPlugins
|
||||
]
|
||||
},
|
||||
preloadViteConfig
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
preloadViteConfig.plugins = builtInPreloadPlugins.concat(preloadViteConfig.plugins)
|
||||
|
||||
loadResult.config.preload = preloadViteConfig
|
||||
loadResult.config.preload.configFile = false
|
||||
}
|
||||
|
||||
if (loadResult.config.renderer) {
|
||||
const rendererViteConfig: ViteConfig = mergeConfig(loadResult.config.renderer, deepClone(config))
|
||||
const rendererViteConfig: RendererViteConfig = mergeConfig(loadResult.config.renderer, deepClone(config))
|
||||
|
||||
rendererViteConfig.mode = inlineConfig.mode || rendererViteConfig.mode || defaultMode
|
||||
|
||||
if (outDir) {
|
||||
resetOutDir(rendererViteConfig, outDir, 'renderer')
|
||||
}
|
||||
|
||||
mergePlugins(rendererViteConfig, electronRendererVitePlugin({ root }))
|
||||
const builtInRendererPlugins: PluginOption[] = [
|
||||
electronRendererConfigPresetPlugin({ root }),
|
||||
electronRendererConfigValidatorPlugin()
|
||||
]
|
||||
|
||||
if (rendererViteConfig.build?.isolatedEntries) {
|
||||
builtInRendererPlugins.push(
|
||||
isolateEntriesPlugin(
|
||||
mergeConfig(
|
||||
{
|
||||
plugins: [electronRendererConfigPresetPlugin({ root })]
|
||||
},
|
||||
rendererViteConfig
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
rendererViteConfig.plugins = builtInRendererPlugins.concat(rendererViteConfig.plugins || [])
|
||||
|
||||
loadResult.config.renderer = rendererViteConfig
|
||||
loadResult.config.renderer.configFile = false
|
||||
}
|
||||
|
||||
userConfig = loadResult.config
|
||||
@ -176,10 +322,6 @@ export async function resolveConfig(
|
||||
return resolved
|
||||
}
|
||||
|
||||
function deepClone<T>(data: T): T {
|
||||
return JSON.parse(JSON.stringify(data))
|
||||
}
|
||||
|
||||
function resetOutDir(config: ViteConfig, outDir: string, subOutDir: string): void {
|
||||
let userOutDir = config.build?.outDir
|
||||
if (outDir === userOutDir) {
|
||||
@ -192,9 +334,36 @@ function resetOutDir(config: ViteConfig, outDir: string, subOutDir: string): voi
|
||||
}
|
||||
}
|
||||
|
||||
function mergePlugins(config: ViteConfig, plugins: Plugin[]): void {
|
||||
const userPlugins = config.plugins || []
|
||||
config.plugins = userPlugins.concat(plugins)
|
||||
async function resolveConfigDrivenPlugins(config: MainViteConfig | PreloadViteConfig): Promise<PluginOption[]> {
|
||||
const userPlugins = (await asyncFlatten(config.plugins || [])).filter(Boolean) as Plugin[]
|
||||
|
||||
const configDrivenPlugins: PluginOption[] = []
|
||||
|
||||
const hasExternalizeDepsPlugin = userPlugins.some(p => p.name === 'vite:externalize-deps')
|
||||
if (!hasExternalizeDepsPlugin) {
|
||||
const externalOptions = config.build?.externalizeDeps ?? true
|
||||
if (externalOptions) {
|
||||
isOptions<ExternalOptions>(externalOptions)
|
||||
? configDrivenPlugins.push(externalizeDepsPlugin(externalOptions))
|
||||
: configDrivenPlugins.push(externalizeDepsPlugin())
|
||||
}
|
||||
}
|
||||
|
||||
const hasBytecodePlugin = userPlugins.some(p => p.name === 'vite:bytecode')
|
||||
if (!hasBytecodePlugin) {
|
||||
const bytecodeOptions = config.build?.bytecode
|
||||
if (bytecodeOptions) {
|
||||
isOptions<BytecodeOptions>(bytecodeOptions)
|
||||
? configDrivenPlugins.push(bytecodePlugin(bytecodeOptions))
|
||||
: configDrivenPlugins.push(bytecodePlugin())
|
||||
}
|
||||
}
|
||||
|
||||
return configDrivenPlugins
|
||||
}
|
||||
|
||||
function isOptions<T extends object>(value: boolean | T): value is T {
|
||||
return typeof value === 'object' && value !== null
|
||||
}
|
||||
|
||||
const CONFIG_FILE_NAME = 'electron.vite.config'
|
||||
@ -226,70 +395,30 @@ export async function loadConfigFromFile(
|
||||
}
|
||||
}
|
||||
|
||||
// electron does not support adding type: "module" to package.json
|
||||
let isESM = false
|
||||
if (/\.m[jt]s$/.test(resolvedPath) || resolvedPath.endsWith('.ts')) {
|
||||
isESM = true
|
||||
}
|
||||
const isESM = isFilePathESM(resolvedPath)
|
||||
|
||||
try {
|
||||
const bundled = await bundleConfigFile(resolvedPath, isESM)
|
||||
const userConfig = await loadConfigFormBundledFile(configRoot, resolvedPath, bundled.code, isESM)
|
||||
const { code, dependencies } = await bundleConfigFile(resolvedPath, isESM)
|
||||
const configExport = await loadConfigFormBundledFile(configRoot, resolvedPath, code, isESM)
|
||||
|
||||
const config = await (typeof userConfig === 'function' ? userConfig() : userConfig)
|
||||
const config = await (typeof configExport === 'function' ? configExport(configEnv) : configExport)
|
||||
if (!isObject(config)) {
|
||||
throw new Error(`config must export or return an object`)
|
||||
}
|
||||
|
||||
const configRequired: string[] = []
|
||||
|
||||
let mainConfig
|
||||
if (config.main) {
|
||||
const mainViteConfig = config.main
|
||||
mainConfig = await (typeof mainViteConfig === 'function' ? mainViteConfig(configEnv) : mainViteConfig)
|
||||
if (!isObject(mainConfig)) {
|
||||
throw new Error(`main config must export or return an object`)
|
||||
if (!ignoreConfigWarning) {
|
||||
const missingFields = ['main', 'renderer', 'preload'].filter(field => !config[field])
|
||||
if (missingFields.length > 0) {
|
||||
createLogger(logLevel).warn(
|
||||
`${colors.yellow(colors.bold('(!)'))} ${colors.yellow(`${missingFields.join(' and ')} config is missing`)}\n`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
configRequired.push('main')
|
||||
}
|
||||
|
||||
let rendererConfig
|
||||
if (config.renderer) {
|
||||
const rendererViteConfig = config.renderer
|
||||
rendererConfig = await (typeof rendererViteConfig === 'function'
|
||||
? rendererViteConfig(configEnv)
|
||||
: rendererViteConfig)
|
||||
if (!isObject(rendererConfig)) {
|
||||
throw new Error(`renderer config must export or return an object`)
|
||||
}
|
||||
} else {
|
||||
configRequired.push('renderer')
|
||||
}
|
||||
|
||||
let preloadConfig
|
||||
if (config.preload) {
|
||||
const preloadViteConfig = config.preload
|
||||
preloadConfig = await (typeof preloadViteConfig === 'function' ? preloadViteConfig(configEnv) : preloadViteConfig)
|
||||
if (!isObject(preloadConfig)) {
|
||||
throw new Error(`preload config must export or return an object`)
|
||||
}
|
||||
} else {
|
||||
configRequired.push('preload')
|
||||
}
|
||||
|
||||
if (!ignoreConfigWarning && configRequired.length > 0) {
|
||||
createLogger(logLevel).warn(colors.yellow(`${configRequired.join(' and ')} config is missing`))
|
||||
}
|
||||
|
||||
return {
|
||||
path: normalizePath(resolvedPath),
|
||||
config: {
|
||||
main: mainConfig,
|
||||
renderer: rendererConfig,
|
||||
preload: preloadConfig
|
||||
},
|
||||
dependencies: bundled.dependencies
|
||||
config,
|
||||
dependencies
|
||||
}
|
||||
} catch (e) {
|
||||
createLogger(logLevel).error(colors.red(`failed to load config from ${resolvedPath}`), { error: e as Error })
|
||||
@ -315,7 +444,7 @@ async function bundleConfigFile(fileName: string, isESM: boolean): Promise<{ cod
|
||||
absWorkingDir: process.cwd(),
|
||||
entryPoints: [fileName],
|
||||
write: false,
|
||||
target: ['node14.18', 'node16'],
|
||||
target: ['node20'],
|
||||
platform: 'node',
|
||||
bundle: true,
|
||||
format: isESM ? 'esm' : 'cjs',
|
||||
@ -378,14 +507,14 @@ async function loadConfigFormBundledFile(
|
||||
configFile: string,
|
||||
bundledCode: string,
|
||||
isESM: boolean
|
||||
): Promise<UserConfigExport> {
|
||||
): 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 dynamicImport(fileUrl)).default
|
||||
return (await import(fileUrl.href)).default
|
||||
} finally {
|
||||
try {
|
||||
fs.unlinkSync(fileNameTmp)
|
||||
|
||||
120
src/electron.ts
120
src/electron.ts
@ -1,25 +1,25 @@
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { type ChildProcessWithoutNullStreams, spawn } from 'node:child_process'
|
||||
import { type Logger } from 'vite'
|
||||
import { type ChildProcess, spawn } from 'node:child_process'
|
||||
import { loadPackageData } from './utils'
|
||||
|
||||
const _require = createRequire(import.meta.url)
|
||||
|
||||
const ensureElectronEntryFile = (root = process.cwd()): void => {
|
||||
const pkg = path.join(root, 'package.json')
|
||||
if (fs.existsSync(pkg)) {
|
||||
const main = require(pkg).main
|
||||
if (!main) {
|
||||
throw new Error('not found an entry point to electorn app, please add main field for your package.json')
|
||||
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, main)
|
||||
const entryPath = path.resolve(root, pkg.main)
|
||||
if (!fs.existsSync(entryPath)) {
|
||||
throw new Error(`not found the electorn app entry file: ${entryPath}`)
|
||||
throw new Error(`No electron app entry file found: ${entryPath}`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('no package.json')
|
||||
throw new Error('Not found: package.json')
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,16 @@ const getElectronMajorVer = (): string => {
|
||||
return majorVer
|
||||
}
|
||||
|
||||
export function supportESM(): boolean {
|
||||
const majorVer = getElectronMajorVer()
|
||||
return parseInt(majorVer) >= 28
|
||||
}
|
||||
|
||||
export function supportImportMetaPaths(): boolean {
|
||||
const majorVer = getElectronMajorVer()
|
||||
return parseInt(majorVer) >= 30
|
||||
}
|
||||
|
||||
export function getElectronPath(): string {
|
||||
let electronExecPath = process.env.ELECTRON_EXEC_PATH || ''
|
||||
if (!electronExecPath) {
|
||||
@ -59,17 +69,24 @@ export function getElectronNodeTarget(): string {
|
||||
const electronVer = getElectronMajorVer()
|
||||
|
||||
const nodeVer = {
|
||||
'21': '16.16',
|
||||
'20': '16.15',
|
||||
'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'
|
||||
'39': '22.20',
|
||||
'38': '22.19',
|
||||
'37': '22.16',
|
||||
'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'
|
||||
}
|
||||
if (electronVer && parseInt(electronVer) > 10) {
|
||||
let target = nodeVer[electronVer]
|
||||
@ -83,17 +100,24 @@ export function getElectronChromeTarget(): string {
|
||||
const electronVer = getElectronMajorVer()
|
||||
|
||||
const chromeVer = {
|
||||
'21': '106',
|
||||
'20': '104',
|
||||
'19': '102',
|
||||
'18': '100',
|
||||
'17': '98',
|
||||
'16': '96',
|
||||
'15': '94',
|
||||
'14': '93',
|
||||
'13': '91',
|
||||
'12': '89',
|
||||
'11': '87'
|
||||
'39': '142',
|
||||
'38': '140',
|
||||
'37': '138',
|
||||
'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'
|
||||
}
|
||||
if (electronVer && parseInt(electronVer) > 10) {
|
||||
let target = chromeVer[electronVer]
|
||||
@ -103,18 +127,34 @@ export function getElectronChromeTarget(): string {
|
||||
return ''
|
||||
}
|
||||
|
||||
export function startElectron(root: string | undefined, logger: Logger): ChildProcessWithoutNullStreams {
|
||||
export function startElectron(root: string | undefined): ChildProcess {
|
||||
ensureElectronEntryFile(root)
|
||||
|
||||
const electronPath = getElectronPath()
|
||||
|
||||
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())
|
||||
})
|
||||
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,8 +1,9 @@
|
||||
export { type LogLevel, createLogger, splitVendorChunkPlugin, splitVendorChunk } from 'vite'
|
||||
export { type LogLevel, createLogger, mergeConfig } from 'vite'
|
||||
export * from './config'
|
||||
export { createServer } from './server'
|
||||
export { build } from './build'
|
||||
export { preview } from './preview'
|
||||
export { loadEnv } from './utils'
|
||||
export * from './plugins/bytecode'
|
||||
export * from './plugins/externalizeDeps'
|
||||
export * from './plugins/swc'
|
||||
|
||||
144
src/plugins/asset.ts
Normal file
144
src/plugins/asset.ts
Normal file
@ -0,0 +1,144 @@
|
||||
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, getHash, toRelativePath } from '../utils'
|
||||
import { supportImportMetaPaths } from '../electron'
|
||||
|
||||
const nodeAssetRE = /__VITE_NODE_ASSET__([\w$]+)__/g
|
||||
const nodePublicAssetRE = /__VITE_NODE_PUBLIC_ASSET__([a-z\d]{8})__/g
|
||||
|
||||
const assetImportRE = /(?:[?|&]asset(?:&|$)|\.wasm\?loader$|\.node$)/
|
||||
const assetRE = /[?|&]asset(?:&|$)/
|
||||
const assetUnpackRE = /[?|&]asset&asarUnpack$/
|
||||
|
||||
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 publicDir = ''
|
||||
const publicAssetPathCache = new Map<string, string>()
|
||||
const assetCache = new Map<string, string>()
|
||||
const isImportMetaPathSupported = supportImportMetaPaths()
|
||||
return {
|
||||
name: 'vite:node-asset',
|
||||
apply: 'build',
|
||||
enforce: 'pre',
|
||||
buildStart(): void {
|
||||
publicAssetPathCache.clear()
|
||||
assetCache.clear()
|
||||
},
|
||||
configResolved(config): void {
|
||||
publicDir = config.publicDir
|
||||
},
|
||||
resolveId(id): string | void {
|
||||
if (id === wasmHelperId) {
|
||||
return id
|
||||
}
|
||||
},
|
||||
async load(id): Promise<string | void> {
|
||||
if (id === wasmHelperId) {
|
||||
return wasmHelperCode
|
||||
}
|
||||
|
||||
if (id.startsWith('\0') || !assetImportRE.test(id)) {
|
||||
return
|
||||
}
|
||||
|
||||
let referenceId: string
|
||||
const file = cleanUrl(id)
|
||||
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 (assetRE.test(id)) {
|
||||
const dirnameExpr = isImportMetaPathSupported ? 'import.meta.dirname' : '__dirname'
|
||||
if (assetUnpackRE.test(id)) {
|
||||
return `
|
||||
import { join } from 'path'
|
||||
export default join(${dirnameExpr}, ${referenceId}).replace('app.asar', 'app.asar.unpacked')`
|
||||
} else {
|
||||
return `
|
||||
import { join } from 'path'
|
||||
export default join(${dirnameExpr}, ${referenceId})`
|
||||
}
|
||||
}
|
||||
|
||||
if (id.endsWith('.node')) {
|
||||
return `export default require(${referenceId})`
|
||||
}
|
||||
|
||||
if (id.endsWith('.wasm?loader')) {
|
||||
return `
|
||||
import loadWasm from ${JSON.stringify(wasmHelperId)}
|
||||
export default importObject => loadWasm(${referenceId}, importObject)`
|
||||
}
|
||||
},
|
||||
renderChunk(code, chunk, { sourcemap, dir }): { code: string; map: SourceMapInput } | null {
|
||||
let match: RegExpExecArray | null
|
||||
let s: MagicString | undefined
|
||||
|
||||
nodeAssetRE.lastIndex = 0
|
||||
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
|
||||
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(dir!, 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
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/plugins/buildReporter.ts
Normal file
30
src/plugins/buildReporter.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { type Plugin } from 'vite'
|
||||
|
||||
type BuildReporterApi = {
|
||||
getWatchFiles: () => string[]
|
||||
}
|
||||
|
||||
export default function buildReporterPlugin(): Plugin<BuildReporterApi> {
|
||||
const moduleIds: string[] = []
|
||||
return {
|
||||
name: 'vite:build-reporter',
|
||||
|
||||
buildEnd() {
|
||||
const allModuleIds = Array.from(this.getModuleIds())
|
||||
const sourceFiles = allModuleIds.filter(id => {
|
||||
if (id.includes('node_modules')) {
|
||||
return false
|
||||
}
|
||||
const info = this.getModuleInfo(id)
|
||||
return info && !info.isExternal
|
||||
})
|
||||
moduleIds.push(...sourceFiles)
|
||||
},
|
||||
|
||||
api: {
|
||||
getWatchFiles() {
|
||||
return moduleIds
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,20 @@
|
||||
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 } from 'vite'
|
||||
import { type Plugin, type Logger, type LibraryOptions, normalizePath } from 'vite'
|
||||
import * as babel from '@babel/core'
|
||||
import MagicString from 'magic-string'
|
||||
import type { SourceMapInput, OutputChunk, OutputOptions } 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.resolve(process.cwd(), 'node_modules', 'electron-vite', 'bin', 'electron-bytecode.js')
|
||||
return path.join(path.dirname(_require.resolve('electron-vite/package.json')), 'bin', 'electron-bytecode.cjs')
|
||||
}
|
||||
|
||||
function compileToBytecode(code: string): Promise<Buffer> {
|
||||
@ -93,7 +97,7 @@ const bytecodeModuleLoaderCode = [
|
||||
` ret |= buffer[0];`,
|
||||
` return ret;`,
|
||||
`};`,
|
||||
`Module._extensions[".jsc"] = function (module, filename) {`,
|
||||
`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.");`,
|
||||
@ -136,125 +140,160 @@ const bytecodeModuleLoaderCode = [
|
||||
`};`
|
||||
]
|
||||
|
||||
const bytecodeChunkExtensionRE = /.(jsc|cjsc)$/
|
||||
|
||||
export interface BytecodeOptions {
|
||||
chunkAlias?: string | string[]
|
||||
transformArrowFunctions?: boolean
|
||||
removeBundleJS?: boolean
|
||||
protectedStrings?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile to v8 bytecode to protect source code.
|
||||
* Compile source code to v8 bytecode.
|
||||
*
|
||||
* @deprecated use `build.bytecode` config option instead
|
||||
*/
|
||||
export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
|
||||
if (process.env.NODE_ENV_ELECTRON_VITE !== 'production') {
|
||||
return null
|
||||
}
|
||||
|
||||
const { chunkAlias = [], transformArrowFunctions = true, removeBundleJS = true } = options
|
||||
const { chunkAlias = [], transformArrowFunctions = true, removeBundleJS = true, protectedStrings = [] } = options
|
||||
const _chunkAlias = Array.isArray(chunkAlias) ? chunkAlias : [chunkAlias]
|
||||
|
||||
const bytecodeChunks: string[] = []
|
||||
|
||||
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 plugins: babel.PluginItem[] = []
|
||||
|
||||
if (transformArrowFunctions) {
|
||||
plugins.push('@babel/plugin-transform-arrow-functions')
|
||||
}
|
||||
|
||||
const requireBytecodeLoaderStr = '"use strict";\nrequire("./bytecode-loader.js");'
|
||||
if (protectedStrings.length > 0) {
|
||||
plugins.push([protectStringsPlugin, { protectedStrings: new Set(protectedStrings) }])
|
||||
}
|
||||
|
||||
let config: ResolvedConfig
|
||||
let useInRenderer = false
|
||||
let bytecodeFiles: { name: string; size: number }[] = []
|
||||
const shouldTransformBytecodeChunk = plugins.length !== 0
|
||||
|
||||
const _transform = (code: string, sourceMaps: boolean = false): { code: string; map?: SourceMapInput } | null => {
|
||||
const re = babel.transform(code, { plugins, sourceMaps })
|
||||
return re ? { code: re.code || '', map: re.map } : null
|
||||
}
|
||||
|
||||
const useStrict = '"use strict";'
|
||||
const bytecodeModuleLoader = 'bytecode-loader.cjs'
|
||||
|
||||
let logger: Logger
|
||||
let supported = false
|
||||
|
||||
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')
|
||||
configResolved(config): void {
|
||||
if (supported) {
|
||||
return
|
||||
}
|
||||
logger = config.logger
|
||||
const useInRenderer = config.plugins.some(p => p.name === 'vite:electron-renderer-preset-config')
|
||||
if (useInRenderer) {
|
||||
config.logger.warn(colors.yellow('bytecodePlugin is not support renderers'))
|
||||
config.logger.warn(colors.yellow('bytecodePlugin does not support renderer.'))
|
||||
return
|
||||
}
|
||||
const build = config.build
|
||||
const resolvedOutputs = resolveBuildOutputs(build.rollupOptions.output, build.lib)
|
||||
if (resolvedOutputs) {
|
||||
const outputs = Array.isArray(resolvedOutputs) ? resolvedOutputs : [resolvedOutputs]
|
||||
const output = outputs[0]
|
||||
if (output.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".'
|
||||
)
|
||||
)
|
||||
}
|
||||
supported = output.format === 'cjs' && !useInRenderer
|
||||
}
|
||||
},
|
||||
renderChunk(code, chunk): { code: string } | null {
|
||||
if (useInRenderer) {
|
||||
return null
|
||||
}
|
||||
if (chunk.type === 'chunk' && isBytecodeChunk(chunk.name)) {
|
||||
bytecodeChunks.push(chunk.fileName)
|
||||
if (transformArrowFunctions) {
|
||||
return {
|
||||
code: _transform(code)
|
||||
}
|
||||
}
|
||||
renderChunk(code, chunk, { sourcemap }): { code: string; map?: SourceMapInput } | null {
|
||||
if (supported && isBytecodeChunk(chunk.name) && shouldTransformBytecodeChunk) {
|
||||
return _transform(code, !!sourcemap)
|
||||
}
|
||||
return null
|
||||
},
|
||||
generateBundle(): void {
|
||||
if (!useInRenderer && bytecodeChunks.length) {
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
source: bytecodeModuleLoaderCode.join('\n') + '\n',
|
||||
name: 'Bytecode Loader File',
|
||||
fileName: 'bytecode-loader.js'
|
||||
})
|
||||
}
|
||||
},
|
||||
async writeBundle(options, output): Promise<void> {
|
||||
if (useInRenderer || bytecodeChunks.length === 0) {
|
||||
async generateBundle(_, output): Promise<void> {
|
||||
if (!supported) {
|
||||
return
|
||||
}
|
||||
const bundles = Object.keys(output)
|
||||
const outDir = options.dir!
|
||||
bytecodeFiles = []
|
||||
const bytecodeRE = new RegExp(bytecodeChunks.map(chunk => `(${chunk})`).join('|'), 'g')
|
||||
const keepBundle = (chunkFileName: string): void => {
|
||||
const newFileName = path.resolve(path.dirname(chunkFileName), `_${path.basename(chunkFileName)}`)
|
||||
fs.renameSync(chunkFileName, newFileName)
|
||||
const _chunks = Object.values(output)
|
||||
const chunks = _chunks.filter(chunk => chunk.type === 'chunk' && isBytecodeChunk(chunk.name)) as OutputChunk[]
|
||||
|
||||
if (chunks.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
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 getBytecodeLoaderBlock = (chunkFileName: string): string => {
|
||||
return `require("${toRelativePath(bytecodeModuleLoader, normalizePath(chunkFileName))}");`
|
||||
}
|
||||
|
||||
let bytecodeChunkCount = 0
|
||||
|
||||
const bundles = Object.keys(output)
|
||||
|
||||
await Promise.all(
|
||||
bundles.map(async name => {
|
||||
const chunk = output[name]
|
||||
if (chunk.type === 'chunk') {
|
||||
let _code = chunk.code
|
||||
if (_code.match(bytecodeRE)) {
|
||||
if (bytecodeRE) {
|
||||
let match: RegExpExecArray | null
|
||||
const s = new MagicString(_code)
|
||||
let s: MagicString | undefined
|
||||
while ((match = bytecodeRE.exec(_code))) {
|
||||
const [chunkName] = match
|
||||
s.overwrite(match.index, match.index + chunkName.length, chunkName + 'c', {
|
||||
s ||= new MagicString(_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()
|
||||
if (s) {
|
||||
_code = s.toString()
|
||||
}
|
||||
}
|
||||
const chunkFileName = path.resolve(outDir, name)
|
||||
if (bytecodeChunks.includes(name)) {
|
||||
const bytecodeBuffer = await compileToBytecode(_code)
|
||||
const bytecodeFileName = path.resolve(outDir, name + 'c')
|
||||
fs.writeFileSync(bytecodeFileName, bytecodeBuffer)
|
||||
if (chunk.isEntry) {
|
||||
if (!removeBundleJS) {
|
||||
keepBundle(chunkFileName)
|
||||
}
|
||||
const code = requireBytecodeLoaderStr + `\nrequire("./${normalizePath(name + 'c')}");\n`
|
||||
fs.writeFileSync(chunkFileName, code)
|
||||
} else {
|
||||
if (removeBundleJS) {
|
||||
fs.unlinkSync(chunkFileName)
|
||||
} else {
|
||||
keepBundle(chunkFileName)
|
||||
}
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: name + 'c',
|
||||
source: bytecodeBuffer
|
||||
})
|
||||
if (!removeBundleJS) {
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: '_' + chunk.fileName,
|
||||
source: chunk.code
|
||||
})
|
||||
}
|
||||
bytecodeFiles.push({ name: name + 'c', size: bytecodeBuffer.length })
|
||||
if (chunk.isEntry) {
|
||||
const bytecodeLoaderBlock = getBytecodeLoaderBlock(chunk.fileName)
|
||||
const bytecodeModuleBlock = `require("./${path.basename(name) + 'c'}");`
|
||||
const code = `${useStrict}\n${bytecodeLoaderBlock}\n${bytecodeModuleBlock}\n`
|
||||
chunk.code = code
|
||||
} else {
|
||||
delete output[chunk.fileName]
|
||||
}
|
||||
bytecodeChunkCount += 1
|
||||
} else {
|
||||
if (chunk.isEntry) {
|
||||
let hasBytecodeMoudle = false
|
||||
@ -271,33 +310,118 @@ export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null {
|
||||
for (const importerId of dynamicImporters) idsToHandle.add(importerId)
|
||||
}
|
||||
}
|
||||
_code = hasBytecodeMoudle ? _code.replace('"use strict";', requireBytecodeLoaderStr) : _code
|
||||
_code = hasBytecodeMoudle
|
||||
? _code.replace(
|
||||
/("use strict";)|('use strict';)/,
|
||||
`${useStrict}\n${getBytecodeLoaderBlock(chunk.fileName)}`
|
||||
)
|
||||
: _code
|
||||
}
|
||||
fs.writeFileSync(chunkFileName, _code)
|
||||
chunk.code = _code
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if (bytecodeChunkCount && !_chunks.some(ass => ass.type === 'asset' && ass.fileName === bytecodeModuleLoader)) {
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
source: bytecodeModuleLoaderCode.join('\n') + '\n',
|
||||
name: 'Bytecode Loader File',
|
||||
fileName: bytecodeModuleLoader
|
||||
})
|
||||
}
|
||||
},
|
||||
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 kibs = file.size / 1024
|
||||
config.logger.info(
|
||||
`${colors.gray(colors.white(colors.dim(outDir)))}${colors.green(file.name.padEnd(longest + 2))} ${
|
||||
kibs > chunkLimit ? colors.yellow(`${kibs.toFixed(2)} KiB`) : colors.dim(`${kibs.toFixed(2)} KiB`)
|
||||
}`
|
||||
)
|
||||
})
|
||||
bytecodeFiles = []
|
||||
writeBundle(_, output): void {
|
||||
if (supported) {
|
||||
const bytecodeChunkCount = Object.keys(output).filter(chunk => bytecodeChunkExtensionRE.test(chunk)).length
|
||||
logger.info(`${colors.green(`✓`)} ${bytecodeChunkCount} chunks compiled into bytecode.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
interface ProtectStringsPluginState extends babel.PluginPass {
|
||||
opts: { protectedStrings: Set<string> }
|
||||
}
|
||||
|
||||
function protectStringsPlugin(api: typeof babel & babel.ConfigAPI): babel.PluginObj<ProtectStringsPluginState> {
|
||||
const { types: t } = api
|
||||
|
||||
function createFromCharCodeFunction(value: string): babel.types.CallExpression {
|
||||
const charCodes = Array.from(value).map(s => s.charCodeAt(0))
|
||||
const charCodeLiterals = charCodes.map(code => t.numericLiteral(code))
|
||||
|
||||
// String.fromCharCode
|
||||
const memberExpression = t.memberExpression(t.identifier('String'), t.identifier('fromCharCode'))
|
||||
// String.fromCharCode(...arr)
|
||||
const callExpression = t.callExpression(memberExpression, [t.spreadElement(t.identifier('arr'))])
|
||||
// return String.fromCharCode(...arr)
|
||||
const returnStatement = t.returnStatement(callExpression)
|
||||
// function (arr) { return ... }
|
||||
const functionExpression = t.functionExpression(null, [t.identifier('arr')], t.blockStatement([returnStatement]))
|
||||
|
||||
// (function(...) { ... })([x, x, x])
|
||||
return t.callExpression(functionExpression, [t.arrayExpression(charCodeLiterals)])
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'protect-strings-plugin',
|
||||
visitor: {
|
||||
StringLiteral(path, state) {
|
||||
// obj['property']
|
||||
if (path.parentPath.isMemberExpression({ property: path.node, computed: true })) {
|
||||
return
|
||||
}
|
||||
|
||||
// { 'key': value }
|
||||
if (path.parentPath.isObjectProperty({ key: path.node, computed: false })) {
|
||||
return
|
||||
}
|
||||
|
||||
// require('fs')
|
||||
if (
|
||||
path.parentPath.isCallExpression() &&
|
||||
t.isIdentifier(path.parentPath.node.callee) &&
|
||||
path.parentPath.node.callee.name === 'require' &&
|
||||
path.parentPath.node.arguments[0] === path.node
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Only CommonJS is supported, import declaration and export declaration checks are ignored
|
||||
|
||||
const { value } = path.node
|
||||
if (state.opts.protectedStrings.has(value)) {
|
||||
path.replaceWith(createFromCharCodeFunction(value))
|
||||
}
|
||||
},
|
||||
TemplateLiteral(path, state) {
|
||||
// Must be a pure static template literal
|
||||
// expressions must be empty (no ${variables})
|
||||
// quasis must have only one element (meaning the entire string is a single static part).
|
||||
if (path.node.expressions.length > 0 || path.node.quasis.length !== 1) {
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the raw value of the template literal
|
||||
// path.node.quasis[0].value.raw is used to get the raw string, including escape sequences
|
||||
// path.node.quasis[0].value.cooked is used to get the processed/cooked string (with escape sequences handled)
|
||||
const value = path.node.quasis[0].value.cooked
|
||||
if (value && state.opts.protectedStrings.has(value)) {
|
||||
path.replaceWith(createFromCharCodeFunction(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,14 +2,16 @@ import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import { builtinModules } from 'node:module'
|
||||
import colors from 'picocolors'
|
||||
import { type Plugin, mergeConfig, normalizePath } from 'vite'
|
||||
import { getElectronNodeTarget, getElectronChromeTarget } from '../electron'
|
||||
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 {
|
||||
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}`)
|
||||
@ -18,7 +20,7 @@ function findLibEntry(root: string, scope: string): string {
|
||||
}
|
||||
}
|
||||
}
|
||||
return ''
|
||||
return undefined
|
||||
}
|
||||
|
||||
function findInput(root: string, scope = 'renderer'): string {
|
||||
@ -37,261 +39,374 @@ function processEnvDefine(): Record<string, string> {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
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
|
||||
}
|
||||
|
||||
const nodeTarget = getElectronNodeTarget()
|
||||
export function electronMainConfigPresetPlugin(options?: ElectronPluginOptions): Plugin {
|
||||
return {
|
||||
name: 'vite:electron-main-config-preset',
|
||||
apply: 'build',
|
||||
enforce: 'pre',
|
||||
config(config): void {
|
||||
const root = options?.root || process.cwd()
|
||||
|
||||
const defaultConfig = {
|
||||
build: {
|
||||
outDir: path.resolve(root, 'out', 'main'),
|
||||
target: nodeTarget,
|
||||
assetsDir: 'chunks',
|
||||
rollupOptions: {
|
||||
external: ['electron', ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
||||
output: {
|
||||
entryFileNames: '[name].js'
|
||||
}
|
||||
},
|
||||
reportCompressedSize: false,
|
||||
minify: false
|
||||
}
|
||||
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) {
|
||||
defaultConfig.build['lib'] = {
|
||||
entry: findLibEntry(root, 'main'),
|
||||
formats: ['cjs']
|
||||
}
|
||||
} else {
|
||||
defaultConfig.build.rollupOptions.output['format'] = 'cjs'
|
||||
}
|
||||
|
||||
const buildConfig = mergeConfig(defaultConfig.build, build)
|
||||
config.build = buildConfig
|
||||
|
||||
config.define = config.define || {}
|
||||
config.define = { ...processEnvDefine(), ...config.define }
|
||||
|
||||
config.envPrefix = config.envPrefix || 'MAIN_VITE_'
|
||||
}
|
||||
},
|
||||
{
|
||||
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) {
|
||||
const rollupOptions = build.rollupOptions
|
||||
if (!rollupOptions?.input) {
|
||||
throw new Error('build lib field required for the electron vite main config')
|
||||
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 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function electronMainConfigValidatorPlugin(): Plugin {
|
||||
return {
|
||||
name: 'vite:electron-main-config-validator',
|
||||
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 {
|
||||
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 main config output format must be cjs')
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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')
|
||||
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()
|
||||
export function electronPreloadConfigPresetPlugin(options?: ElectronPluginOptions): Plugin {
|
||||
return {
|
||||
name: 'vite:electron-preload-config-preset',
|
||||
apply: 'build',
|
||||
enforce: 'pre',
|
||||
config(config): void {
|
||||
const root = options?.root || process.cwd()
|
||||
|
||||
const nodeTarget = getElectronNodeTarget()
|
||||
const nodeTarget = getElectronNodeTarget()
|
||||
|
||||
const defaultConfig = {
|
||||
build: {
|
||||
outDir: path.resolve(root, 'out', 'preload'),
|
||||
target: nodeTarget,
|
||||
assetsDir: 'chunks',
|
||||
rollupOptions: {
|
||||
external: ['electron', ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
||||
output: {
|
||||
entryFileNames: '[name].js'
|
||||
}
|
||||
},
|
||||
reportCompressedSize: false,
|
||||
minify: false
|
||||
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) {
|
||||
defaultConfig.build['lib'] = {
|
||||
entry: findLibEntry(root, 'preload'),
|
||||
formats: ['cjs']
|
||||
}
|
||||
} else {
|
||||
defaultConfig.build.rollupOptions.output['format'] = 'cjs'
|
||||
}
|
||||
|
||||
const buildConfig = mergeConfig(defaultConfig.build, build)
|
||||
config.build = buildConfig
|
||||
|
||||
config.define = config.define || {}
|
||||
config.define = { ...processEnvDefine(), ...config.define }
|
||||
|
||||
config.envPrefix = config.envPrefix || 'PRELOAD_VITE_'
|
||||
}
|
||||
},
|
||||
{
|
||||
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')
|
||||
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 {
|
||||
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')
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function electronPreloadConfigValidatorPlugin(): Plugin {
|
||||
return {
|
||||
name: 'vite:electron-preload-config-validator',
|
||||
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 {
|
||||
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')
|
||||
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()
|
||||
export function electronRendererConfigPresetPlugin(options?: ElectronPluginOptions): Plugin {
|
||||
return {
|
||||
name: 'vite:electron-renderer-config-preset',
|
||||
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'
|
||||
config.base =
|
||||
config.mode === 'production' || process.env.NODE_ENV_ELECTRON_VITE === 'production' ? './' : config.base
|
||||
config.root = config.root || './src/renderer'
|
||||
|
||||
const chromeTarget = getElectronChromeTarget()
|
||||
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 + '/')
|
||||
const emptyOutDir = (): boolean => {
|
||||
let outDir = config.build?.outDir
|
||||
if (outDir) {
|
||||
if (!path.isAbsolute(outDir)) {
|
||||
outDir = path.resolve(root, outDir)
|
||||
}
|
||||
return true
|
||||
const resolvedRoot = normalizePath(path.resolve(root))
|
||||
return normalizePath(outDir).startsWith(resolvedRoot + '/')
|
||||
}
|
||||
|
||||
const defaultConfig = {
|
||||
build: {
|
||||
outDir: path.resolve(root, 'out', 'renderer'),
|
||||
target: chromeTarget,
|
||||
modulePreload: { polyfill: false },
|
||||
rollupOptions: {
|
||||
input: findInput(root),
|
||||
external: [...builtinModules.flatMap(m => [m, `node:${m}`])]
|
||||
},
|
||||
reportCompressedSize: false,
|
||||
minify: false,
|
||||
emptyOutDir: emptyOutDir()
|
||||
}
|
||||
}
|
||||
|
||||
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
||||
config.build = buildConfig
|
||||
|
||||
config.envDir = config.envDir || path.resolve(root)
|
||||
|
||||
config.envPrefix = config.envPrefix || 'RENDERER_VITE_'
|
||||
return true
|
||||
}
|
||||
},
|
||||
{
|
||||
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 defaultConfig = {
|
||||
build: {
|
||||
outDir: path.resolve(root, 'out', 'renderer'),
|
||||
target: chromeTarget,
|
||||
modulePreload: { polyfill: false },
|
||||
rollupOptions: {
|
||||
input: findInput(root)
|
||||
},
|
||||
reportCompressedSize: false,
|
||||
minify: false,
|
||||
emptyOutDir: emptyOutDir()
|
||||
}
|
||||
}
|
||||
|
||||
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')
|
||||
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_']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function electronRendererConfigValidatorPlugin(): Plugin {
|
||||
return {
|
||||
name: 'vite:electron-renderer-config-validator',
|
||||
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.')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
74
src/plugins/esmShim.ts
Normal file
74
src/plugins/esmShim.ts
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 { supportImportMetaPaths } 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 {
|
||||
const CJSShim = supportImportMetaPaths() ? CJSShim_node_20_11 : CJSShim_normal
|
||||
|
||||
return {
|
||||
name: 'vite:esm-shim',
|
||||
apply: 'build',
|
||||
enforce: 'post',
|
||||
renderChunk(code, _chunk, { format, sourcemap }): { code: string; map?: SourceMapInput } | null {
|
||||
if (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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
import path from 'node:path'
|
||||
import { createRequire } from 'node:module'
|
||||
import { type Plugin, mergeConfig } from 'vite'
|
||||
import { loadPackageData } from '../utils'
|
||||
|
||||
export interface ExternalOptions {
|
||||
exclude?: string[]
|
||||
@ -8,24 +7,26 @@ export interface ExternalOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically externalize dependencies
|
||||
* Automatically externalize dependencies.
|
||||
*
|
||||
* @deprecated use `build.externalizeDeps` config option instead
|
||||
*/
|
||||
export function externalizeDepsPlugin(options: ExternalOptions = {}): Plugin | null {
|
||||
const { exclude = [], include = [] } = options
|
||||
|
||||
const packagePath = path.resolve(process.cwd(), 'package.json')
|
||||
const require = createRequire(import.meta.url)
|
||||
const pkg = require(packagePath)
|
||||
const pkg = loadPackageData() || {}
|
||||
let deps = Object.keys(pkg.dependencies || {})
|
||||
|
||||
if (include.length) {
|
||||
deps = deps.concat(include)
|
||||
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',
|
||||
@ -33,7 +34,7 @@ export function externalizeDepsPlugin(options: ExternalOptions = {}): Plugin | n
|
||||
const defaultConfig = {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: [...new Set(deps)]
|
||||
external: deps.length > 0 ? [...deps, new RegExp(`^(${deps.join('|')})/.+`)] : []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
198
src/plugins/isolateEntries.ts
Normal file
198
src/plugins/isolateEntries.ts
Normal file
@ -0,0 +1,198 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-function-type */
|
||||
import path from 'node:path'
|
||||
import { type InlineConfig, type Plugin, type Logger, type LogLevel, build as viteBuild, mergeConfig } from 'vite'
|
||||
import type { InputOptions, RollupOutput } from 'rollup'
|
||||
import colors from 'picocolors'
|
||||
import buildReporterPlugin from './buildReporter'
|
||||
|
||||
const VIRTUAL_ENTRY_ID = '\0virtual:isolate-entries'
|
||||
|
||||
const LogLevels: Record<LogLevel, number> = {
|
||||
silent: 0,
|
||||
error: 1,
|
||||
warn: 2,
|
||||
info: 3
|
||||
}
|
||||
|
||||
export default function isolateEntriesPlugin(userConfig: InlineConfig): Plugin {
|
||||
let logger: Logger
|
||||
|
||||
let entries: string[] | { [x: string]: string }[]
|
||||
|
||||
let transformedCount = 0
|
||||
|
||||
const assetCache = new Set<string>()
|
||||
|
||||
return {
|
||||
name: 'vite:isolate-entries',
|
||||
apply: 'build',
|
||||
|
||||
configResolved(config): void {
|
||||
logger = config.logger
|
||||
},
|
||||
|
||||
options(opts): InputOptions | void {
|
||||
const { input } = opts
|
||||
if (input && typeof input === 'object') {
|
||||
if ((Array.isArray(input) && input.length > 0) || Object.keys(input).length > 1) {
|
||||
opts.input = VIRTUAL_ENTRY_ID
|
||||
entries = Array.isArray(input) ? input : Object.entries(input).map(([key, value]) => ({ [key]: value }))
|
||||
return opts
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
buildStart(): void {
|
||||
transformedCount = 0
|
||||
assetCache.clear()
|
||||
},
|
||||
|
||||
resolveId(id): string | null {
|
||||
if (id === VIRTUAL_ENTRY_ID) {
|
||||
return id
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
async load(id): Promise<string | void> {
|
||||
if (id === VIRTUAL_ENTRY_ID) {
|
||||
const shouldLog = LogLevels[userConfig.logLevel || 'info'] >= LogLevels.info
|
||||
const shouldWatch = this.meta.watchMode
|
||||
|
||||
const watchFiles = new Set<string>()
|
||||
|
||||
for (const entry of entries) {
|
||||
const re = await bundleEntryFile(entry, userConfig, shouldWatch, shouldLog, transformedCount)
|
||||
|
||||
const outputChunks = re.bundles.output
|
||||
for (const chunk of outputChunks) {
|
||||
if (assetCache.has(chunk.fileName)) {
|
||||
continue
|
||||
}
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: chunk.fileName,
|
||||
source: chunk.type === 'chunk' ? chunk.code : chunk.source
|
||||
})
|
||||
assetCache.add(chunk.fileName)
|
||||
}
|
||||
|
||||
for (const id of re.watchFiles) {
|
||||
watchFiles.add(id)
|
||||
}
|
||||
|
||||
transformedCount += re.transformedCount
|
||||
}
|
||||
|
||||
for (const id of watchFiles) {
|
||||
this.addWatchFile(id)
|
||||
}
|
||||
|
||||
return `
|
||||
// This is the virtual entry file
|
||||
console.log(1)`
|
||||
}
|
||||
},
|
||||
|
||||
renderStart(): void {
|
||||
clearLine(-1)
|
||||
logger.info(`${colors.green(`✓`)} ${transformedCount} modules transformed.`)
|
||||
},
|
||||
|
||||
generateBundle(_, bundle): void {
|
||||
for (const chunkName in bundle) {
|
||||
if (chunkName.includes('virtual_isolate-entries')) {
|
||||
delete bundle[chunkName]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function bundleEntryFile(
|
||||
input: string | Record<string, string>,
|
||||
config: InlineConfig,
|
||||
watch: boolean,
|
||||
shouldLog: boolean,
|
||||
preTransformedCount: number
|
||||
): Promise<{ bundles: RollupOutput; watchFiles: string[]; transformedCount: number }> {
|
||||
const transformReporter = transformReporterPlugin(preTransformedCount, shouldLog)
|
||||
const buildReporter = watch ? buildReporterPlugin() : undefined
|
||||
|
||||
const viteConfig = mergeConfig(config, {
|
||||
build: {
|
||||
write: false,
|
||||
watch: false
|
||||
},
|
||||
plugins: [transformReporter, buildReporter],
|
||||
logLevel: 'warn',
|
||||
configFile: false
|
||||
}) as InlineConfig
|
||||
|
||||
// rewrite the input instead of merging
|
||||
viteConfig.build!.rollupOptions!.input = input
|
||||
|
||||
const bundles = await viteBuild(viteConfig)
|
||||
|
||||
return {
|
||||
bundles: bundles as RollupOutput,
|
||||
watchFiles: buildReporter?.api?.getWatchFiles() || [],
|
||||
transformedCount: transformReporter?.api?.getTransformedCount() || 0
|
||||
}
|
||||
}
|
||||
|
||||
function transformReporterPlugin(
|
||||
preTransformedCount = 0,
|
||||
shouldLog = true
|
||||
): Plugin<{ getTransformedCount: () => number }> {
|
||||
let transformedCount = 0
|
||||
let root
|
||||
const log = throttle(id => {
|
||||
writeLine(`transforming (${preTransformedCount + transformedCount}) ${colors.dim(path.relative(root, id))}`)
|
||||
})
|
||||
return {
|
||||
name: 'vite:transform-reporter',
|
||||
configResolved(config) {
|
||||
root = config.root
|
||||
},
|
||||
transform(_, id) {
|
||||
transformedCount++
|
||||
if (!shouldLog) return
|
||||
if (id.includes('?')) return
|
||||
log(id)
|
||||
},
|
||||
api: {
|
||||
getTransformedCount() {
|
||||
return transformedCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeLine(output: string): void {
|
||||
clearLine()
|
||||
if (output.length < process.stdout.columns) {
|
||||
process.stdout.write(output)
|
||||
} else {
|
||||
process.stdout.write(output.substring(0, process.stdout.columns - 1))
|
||||
}
|
||||
}
|
||||
|
||||
function clearLine(move: number = 0): void {
|
||||
if (move < 0) {
|
||||
process.stdout.moveCursor(0, move)
|
||||
}
|
||||
process.stdout.clearLine(0)
|
||||
process.stdout.cursorTo(0)
|
||||
}
|
||||
|
||||
function throttle(fn: Function) {
|
||||
let timerHandle: NodeJS.Timeout | null = null
|
||||
return (...args: any[]) => {
|
||||
if (timerHandle) return
|
||||
fn(...args)
|
||||
timerHandle = setTimeout(() => {
|
||||
timerHandle = null
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
123
src/plugins/modulePath.ts
Normal file
123
src/plugins/modulePath.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import path from 'node:path'
|
||||
import { type Plugin, type InlineConfig, build as viteBuild, mergeConfig } from 'vite'
|
||||
import type { SourceMapInput, RollupOutput, OutputOptions } from 'rollup'
|
||||
import MagicString from 'magic-string'
|
||||
import buildReporterPlugin from './buildReporter'
|
||||
import { cleanUrl, toRelativePath } from '../utils'
|
||||
import { supportImportMetaPaths } from '../electron'
|
||||
|
||||
const modulePathRE = /__VITE_MODULE_PATH__([\w$]+)__/g
|
||||
|
||||
/**
|
||||
* Resolve `?modulePath` import and return the module bundle path.
|
||||
*/
|
||||
export default function modulePathPlugin(config: InlineConfig): Plugin {
|
||||
const isImportMetaPathSupported = supportImportMetaPaths()
|
||||
const assetCache = new Set<string>()
|
||||
return {
|
||||
name: 'vite:module-path',
|
||||
apply: 'build',
|
||||
enforce: 'pre',
|
||||
buildStart(): void {
|
||||
assetCache.clear()
|
||||
},
|
||||
async load(id): Promise<string | void> {
|
||||
if (id.endsWith('?modulePath')) {
|
||||
// id resolved by Vite resolve plugin
|
||||
const re = await bundleEntryFile(cleanUrl(id), config, this.meta.watchMode)
|
||||
const [outputChunk, ...outputChunks] = re.bundles.output
|
||||
const hash = this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: outputChunk.fileName,
|
||||
source: outputChunk.code
|
||||
})
|
||||
for (const chunk of outputChunks) {
|
||||
if (assetCache.has(chunk.fileName)) {
|
||||
continue
|
||||
}
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: chunk.fileName,
|
||||
source: chunk.type === 'chunk' ? chunk.code : chunk.source
|
||||
})
|
||||
assetCache.add(chunk.fileName)
|
||||
}
|
||||
for (const id of re.watchFiles) {
|
||||
this.addWatchFile(id)
|
||||
}
|
||||
const refId = `__VITE_MODULE_PATH__${hash}__`
|
||||
const dirnameExpr = isImportMetaPathSupported ? 'import.meta.dirname' : '__dirname'
|
||||
return `
|
||||
import { join } from 'path'
|
||||
export default join(${dirnameExpr}, ${refId})`
|
||||
}
|
||||
},
|
||||
renderChunk(code, chunk, { sourcemap }): { code: string; map: SourceMapInput } | null {
|
||||
let match: RegExpExecArray | null
|
||||
let s: MagicString | undefined
|
||||
|
||||
modulePathRE.lastIndex = 0
|
||||
while ((match = modulePathRE.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
|
||||
})
|
||||
}
|
||||
|
||||
if (s) {
|
||||
return {
|
||||
code: s.toString(),
|
||||
map: sourcemap ? s.generateMap({ hires: 'boundary' }) : null
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function bundleEntryFile(
|
||||
input: string,
|
||||
config: InlineConfig,
|
||||
watch: boolean
|
||||
): Promise<{ bundles: RollupOutput; watchFiles: string[] }> {
|
||||
const reporter = watch ? buildReporterPlugin() : undefined
|
||||
const viteConfig = mergeConfig(config, {
|
||||
build: {
|
||||
write: false,
|
||||
watch: false
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: 'vite:entry-file-name',
|
||||
outputOptions(output): OutputOptions {
|
||||
if (typeof output.entryFileNames !== 'function' && output.entryFileNames) {
|
||||
output.entryFileNames = '[name]-[hash]' + path.extname(output.entryFileNames)
|
||||
}
|
||||
return output
|
||||
}
|
||||
},
|
||||
reporter
|
||||
],
|
||||
logLevel: 'warn',
|
||||
configFile: false
|
||||
}) as InlineConfig
|
||||
|
||||
// rewrite the input instead of merging
|
||||
const buildOptions = viteConfig.build!
|
||||
buildOptions.rollupOptions = {
|
||||
...buildOptions.rollupOptions,
|
||||
input
|
||||
}
|
||||
|
||||
const bundles = await viteBuild(viteConfig)
|
||||
|
||||
return {
|
||||
bundles: bundles as RollupOutput,
|
||||
watchFiles: reporter?.api?.getWatchFiles() || []
|
||||
}
|
||||
}
|
||||
@ -1,65 +1,61 @@
|
||||
import path from 'node:path'
|
||||
import type { Plugin } from 'vite'
|
||||
import type { SourceMapInput } from 'rollup'
|
||||
import MagicString from 'magic-string'
|
||||
import { cleanUrl, parseRequest } from '../utils'
|
||||
import { cleanUrl, toRelativePath } from '../utils'
|
||||
|
||||
const nodeWorkerAssetUrlRE = /__VITE_NODE_WORKER_ASSET__([a-z\d]{8})__/g
|
||||
const nodeWorkerAssetUrlRE = /__VITE_NODE_WORKER_ASSET__([\w$]+)__/g
|
||||
const nodeWorkerRE = /\?nodeWorker(?:&|$)/
|
||||
const nodeWorkerImporterRE = /(?:\?)nodeWorker&importer=([^&]+)(?:&|$)/
|
||||
|
||||
/**
|
||||
* 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') {
|
||||
if (id.endsWith('?nodeWorker')) {
|
||||
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(require.resolve(${assetRefId}), options); }`
|
||||
if (nodeWorkerRE.test(id)) {
|
||||
const match = nodeWorkerImporterRE.exec(id)
|
||||
if (match) {
|
||||
const hash = this.emitFile({
|
||||
type: 'chunk',
|
||||
id: cleanUrl(id),
|
||||
importer: match[1]
|
||||
})
|
||||
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)
|
||||
renderChunk(code, chunk, { sourcemap }): { code: string; map: SourceMapInput } | null {
|
||||
let match: RegExpExecArray | null
|
||||
let s: MagicString | undefined
|
||||
|
||||
while ((match = nodeWorkerAssetUrlRE.exec(code))) {
|
||||
const [full, hash] = match
|
||||
const filename = this.getFileName(hash)
|
||||
let outputFilepath = path.posix.relative(path.dirname(chunk.fileName), filename)
|
||||
if (!outputFilepath.startsWith('.')) {
|
||||
outputFilepath = './' + outputFilepath
|
||||
}
|
||||
const replacement = JSON.stringify(outputFilepath)
|
||||
s.overwrite(match.index, match.index + full.length, replacement, {
|
||||
contentOnly: true
|
||||
})
|
||||
}
|
||||
nodeWorkerAssetUrlRE.lastIndex = 0
|
||||
while ((match = nodeWorkerAssetUrlRE.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
|
||||
})
|
||||
}
|
||||
|
||||
if (s) {
|
||||
return {
|
||||
code: s.toString(),
|
||||
map: sourcemap ? s.generateMap({ hires: true }) : null
|
||||
map: sourcemap ? s.generateMap({ hires: 'boundary' }) : null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ export async function preview(inlineConfig: InlineConfig = {}, options: { skipBu
|
||||
|
||||
const logger = createLogger(inlineConfig.logLevel)
|
||||
|
||||
startElectron(inlineConfig.root, logger)
|
||||
startElectron(inlineConfig.root)
|
||||
|
||||
logger.info(colors.green(`\nstart electron app...`))
|
||||
logger.info(colors.green(`\nstarting electron app...\n`))
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { ChildProcessWithoutNullStreams } from 'node:child_process'
|
||||
import type { ChildProcess } from 'node:child_process'
|
||||
import {
|
||||
type UserConfig as ViteConfig,
|
||||
type ViteDevServer,
|
||||
createServer as ViteCreateServer,
|
||||
createServer as viteCreateServer,
|
||||
build as viteBuild,
|
||||
createLogger,
|
||||
mergeConfig
|
||||
@ -12,60 +12,71 @@ import { type InlineConfig, resolveConfig } from './config'
|
||||
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')
|
||||
if (config.config) {
|
||||
const logger = createLogger(inlineConfig.logLevel)
|
||||
|
||||
let server: ViteDevServer | undefined
|
||||
let ps: ChildProcessWithoutNullStreams | 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
|
||||
if (mainViteConfig) {
|
||||
if (mainViteConfig && !options.rendererOnly) {
|
||||
const watchHook = (): void => {
|
||||
logger.info(colors.green(`\nrebuild the electron main process successfully`))
|
||||
logger.info(colors.green(`\nelectron main process rebuilt successfully`))
|
||||
|
||||
if (ps) {
|
||||
logger.info(colors.cyan(`\n waiting for electron to exit...`))
|
||||
|
||||
ps.removeAllListeners()
|
||||
ps.kill()
|
||||
ps = startElectron(inlineConfig.root, logger)
|
||||
ps = startElectron(inlineConfig.root)
|
||||
|
||||
logger.info(colors.green(`\nrestart electron app...`))
|
||||
logger.info(colors.green(`\nrestarting electron app...\n`))
|
||||
}
|
||||
}
|
||||
|
||||
await doBuild(mainViteConfig, watchHook)
|
||||
await doBuild(mainViteConfig, watchHook, errorHook)
|
||||
|
||||
logger.info(colors.green(`\nbuild the electron main process successfully`))
|
||||
logger.info(colors.green(`\nelectron main process built successfully`))
|
||||
}
|
||||
|
||||
const preloadViteConfig = config.config?.preload
|
||||
if (preloadViteConfig) {
|
||||
if (preloadViteConfig && !options.rendererOnly) {
|
||||
logger.info(colors.gray(`\n-----\n`))
|
||||
|
||||
const watchHook = (): void => {
|
||||
logger.info(colors.green(`\nrebuild the electron preload files successfully`))
|
||||
logger.info(colors.green(`\nelectron preload scripts rebuilt successfully`))
|
||||
|
||||
if (server) {
|
||||
logger.info(colors.cyan(`\n trigger renderer reload`))
|
||||
logger.info(colors.cyan(`\nreloading electron renderer...\n`))
|
||||
|
||||
server.ws.send({ type: 'full-reload' })
|
||||
}
|
||||
}
|
||||
|
||||
await doBuild(preloadViteConfig, watchHook)
|
||||
await doBuild(preloadViteConfig, watchHook, errorHook)
|
||||
|
||||
logger.info(colors.green(`\nbuild the electron preload files successfully`))
|
||||
logger.info(colors.green(`\nelectron preload scripts built successfully`))
|
||||
}
|
||||
|
||||
if (options.rendererOnly) {
|
||||
logger.warn(
|
||||
`\n${colors.yellow(colors.bold('(!)'))} ${colors.yellow('skipped building main process and preload scripts (using previous build)')}`
|
||||
)
|
||||
}
|
||||
|
||||
const rendererViteConfig = config.config?.renderer
|
||||
if (rendererViteConfig) {
|
||||
logger.info(colors.gray(`\n-----\n`))
|
||||
|
||||
server = await ViteCreateServer(rendererViteConfig)
|
||||
server = await viteCreateServer(rendererViteConfig)
|
||||
|
||||
if (!server.httpServer) {
|
||||
throw new Error('HTTP server not available')
|
||||
@ -83,21 +94,21 @@ export async function createServer(inlineConfig: InlineConfig = {}): Promise<voi
|
||||
const slogger = server.config.logger
|
||||
|
||||
slogger.info(colors.green(`dev server running for the electron renderer process at:\n`), {
|
||||
clear: !slogger.hasWarned
|
||||
clear: !slogger.hasWarned && !options.rendererOnly
|
||||
})
|
||||
|
||||
server.printUrls()
|
||||
}
|
||||
|
||||
ps = startElectron(inlineConfig.root, logger)
|
||||
ps = startElectron(inlineConfig.root)
|
||||
|
||||
logger.info(colors.green(`\nstart electron app...`))
|
||||
logger.info(colors.green(`\nstarting electron app...\n`))
|
||||
}
|
||||
}
|
||||
|
||||
type UserConfig = ViteConfig & { configFile?: string | false }
|
||||
|
||||
async function doBuild(config: UserConfig, watchHook: () => void): Promise<void> {
|
||||
async function doBuild(config: UserConfig, watchHook: () => void, errorHook: (e: Error) => void): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
if (config.build?.watch) {
|
||||
let firstBundle = true
|
||||
@ -120,10 +131,12 @@ async function doBuild(config: UserConfig, watchHook: () => void): Promise<void>
|
||||
})
|
||||
}
|
||||
|
||||
viteBuild(config).then(() => {
|
||||
if (!config.build?.watch) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
viteBuild(config)
|
||||
.then(() => {
|
||||
if (!config.build?.watch) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
.catch(e => errorHook(e))
|
||||
})
|
||||
}
|
||||
|
||||
114
src/utils.ts
114
src/utils.ts
@ -1,11 +1,14 @@
|
||||
import { URL, URLSearchParams } from 'node:url'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-function-type */
|
||||
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> {
|
||||
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 resolveHostname(optionsHost: string | boolean | undefined): string {
|
||||
@ -17,10 +20,103 @@ 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')
|
||||
if (fs.existsSync(pkg)) {
|
||||
const _require = createRequire(import.meta.url)
|
||||
const data = _require(pkg)
|
||||
packageCached = {
|
||||
main: data.main,
|
||||
type: data.type,
|
||||
dependencies: data.dependencies
|
||||
}
|
||||
return packageCached
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function isFilePathESM(filePath: string): boolean {
|
||||
if (/\.m[jt]s$/.test(filePath) || filePath.endsWith('.ts')) {
|
||||
return true
|
||||
} else if (/\.c[jt]s$/.test(filePath)) {
|
||||
return false
|
||||
} else {
|
||||
const pkg = loadPackageData()
|
||||
return pkg?.type === 'module'
|
||||
}
|
||||
}
|
||||
|
||||
type DeepWritable<T> =
|
||||
T extends ReadonlyArray<unknown>
|
||||
? { -readonly [P in keyof T]: DeepWritable<T[P]> }
|
||||
: T extends RegExp
|
||||
? RegExp
|
||||
: T[keyof T] extends Function
|
||||
? T
|
||||
: { -readonly [P in keyof T]: DeepWritable<T[P]> }
|
||||
|
||||
export function deepClone<T>(value: T): DeepWritable<T> {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(v => deepClone(v)) as DeepWritable<T>
|
||||
}
|
||||
if (isObject(value)) {
|
||||
const cloned: Record<string, any> = {}
|
||||
for (const key in value) {
|
||||
cloned[key] = deepClone(value[key])
|
||||
}
|
||||
return cloned as DeepWritable<T>
|
||||
}
|
||||
if (typeof value === 'function') {
|
||||
return value as DeepWritable<T>
|
||||
}
|
||||
if (value instanceof RegExp) {
|
||||
return new RegExp(value) as DeepWritable<T>
|
||||
}
|
||||
if (typeof value === 'object' && value != null) {
|
||||
throw new Error('Cannot deep clone non-plain object')
|
||||
}
|
||||
return value as DeepWritable<T>
|
||||
}
|
||||
|
||||
type AsyncFlatten<T extends unknown[]> = T extends (infer U)[] ? Exclude<Awaited<U>, U[]>[] : never
|
||||
|
||||
export async function asyncFlatten<T extends unknown[]>(arr: T): Promise<AsyncFlatten<T>> {
|
||||
do {
|
||||
arr = (await Promise.all(arr)).flat(Infinity) as any
|
||||
} while (arr.some((v: any) => v?.then))
|
||||
return arr as unknown[] as AsyncFlatten<T>
|
||||
}
|
||||
|
||||
@ -1,23 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2019",
|
||||
"module": "esnext",
|
||||
"lib": ["esnext"],
|
||||
"target": "ES2023",
|
||||
"module": "ESNext",
|
||||
"lib": ["ESNext"],
|
||||
"sourceMap": false,
|
||||
"strict": true,
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitAny": false,
|
||||
"noImplicitReturns": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "dist/types",
|
||||
"outDir": "dist"
|
||||
"noImplicitReturns": true
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": ["src", "rollup.config.ts"]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user