Skip to content

Commit

Permalink
Merge pull request #2 from electron-vite/v0.2.0
Browse files Browse the repository at this point in the history
V0.2.0
  • Loading branch information
caoxiemeihao authored Jan 7, 2024
2 parents 40de94b + 46a2548 commit 39ccef8
Show file tree
Hide file tree
Showing 15 changed files with 456 additions and 39 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.2.0 (2024-01-07)

- ef3bbc1 chore: bump lib-esm to 0.4.2
- a6a796f feat: add test
- 68f177b refactor: use import or require by `options.sandbox`

## 0.1.1 (2024-01-06)

- 69e9f12 fix: adapt rollup commonjs plugin
Expand Down
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,30 @@ import electronPreload from 'vite-plugin-electron-preload'

// vite.config.js
export default {
plugins: [electronPreload()],
plugins: [
electronPreload(/* options */),
],
}
```

## How to work
## API <sub><sup>(Define)</sup></sub>

```ts
import { ipcRenderer } from 'electron'

// ↓↓↓↓ convert to ↓↓↓↓
`electronPreload(options: PreloadOptions)`

const electron = require('electron');
export ipcRenderer = electron.ipcRenderer;
```ts
export interface PreloadOptions {
/**
* Must be consistent with the following config.
*
* ```js
* new BrowserWindow({
* webPreferences: {
* sandbox: boolean
* }
* })
* ```
*/
sandbox?: boolean
type?: 'commonjs' | 'module'
}
```
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vite-plugin-electron-preload",
"version": "0.1.1",
"version": "0.2.0",
"description": "A Vite preset adapted to Electron preload",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand All @@ -22,7 +22,7 @@
"test": "vitest run"
},
"dependencies": {
"lib-esm": "^0.4.1"
"lib-esm": "^0.4.2"
},
"devDependencies": {
"@types/node": "^20.10.6",
Expand Down
102 changes: 77 additions & 25 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,121 @@
import path from 'node:path'
import { createRequire } from 'node:module'
import type { Plugin } from 'vite'
import libEsm from 'lib-esm'
import {
alwaysAvailableModules,
builtins,
commonjsPluginAdapter,
withCommonjsIgnoreBuiltins,
electronModules,
electronRenderer,
electronRendererCjs,
getNodeIntegrationEnabledGuard,
nodeModules,
prefix,
resolvePackageJson,
withExternalBuiltins,
} from './utils'

interface PreloadOptions {
export interface PreloadOptions {
/**
* Auto expose `ipcRenderer` to Renderer process
* `require()` can usable matrix
*
* @example
* // Use the ipcRenderer in renderer.js
* const result = await window.ipcRenderer.invoke('channel');
* @see https://github.com/electron/electron/blob/v30.0.0-nightly.20240104/docs/tutorial/esm.md#preload-scripts
*
* @todo implementation
* ```log
* ┏———————————————————————————————————┳——————————┳———————————┓
* │ webPreferences: { } │ import │ require │
* ┠———————————————————————————————————╂——————————╂———————————┨
* │ nodeIntegration: false(undefined) │ ✘ │ ✔ │
* ┠———————————————————————————————————╂——————————╂———————————┨
* │ nodeIntegration: true │ ✔ │ ✔ │
* ┠———————————————————————————————————╂——————————╂———————————┨
* │ sandbox: true(undefined) │ ✘ │ ✔ │
* ┠———————————————————————————————————╂——————————╂———————————┨
* │ sandbox: false │ ✔ │ ✘ │
* ┠———————————————————————————————————╂——————————╂———————————┨
* │ nodeIntegration: false │ ✘ │ ✔ │
* │ sandbox: true │ │ │
* ┠———————————————————————————————————╂——————————╂———————————┨
* │ nodeIntegration: false │ ✔ │ ✘ │
* │ sandbox: false │ │ │
* ┠———————————————————————————————————╂——————————╂———————————┨
* │ nodeIntegration: true │ ✘ │ ✔ │
* │ sandbox: true │ │ │
* ┠———————————————————————————————————╂——————————╂———————————┨
* │ nodeIntegration: true │ ✔ │ ✔ │
* │ sandbox: false │ │ │
* ┗———————————————————————————————————┸——————————┸———————————┛
* - import(✘): SyntaxError: Cannot use import statement outside a module
* - require(✘): ReferenceError: require is not defined in ES module scope, you can use import instead
* ```
*/
exposeIpcRenderer?: true
sandbox?: boolean
type?: 'commonjs' | 'module'
}

const require = createRequire(import.meta.url)

export default function preload(options: PreloadOptions = {}): Plugin {
const requireAvailable = options.sandbox !== false

return {
name: 'vite-plugin-electron-preload',
// Run before the builtin 'vite:resolve' of Vite
enforce: 'pre',
resolveId(source) {
if (builtins.includes(source)) {
// @see - https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention
return prefix + source
async config(config) {
// @see - https://github.com/vitejs/vite/blob/v5.0.9/packages/vite/src/node/config.ts#L490
const root = config.root ? path.resolve(config.root) : process.cwd()

if (options.type == null) {
const json = await resolvePackageJson(root)
if (json) {
options.type = json.type
}
}
},
config(config) {

if (options.type === 'module') {
// `esm` may need to be converted to `cjs`, depending on `options.sandbox`

// Processed in resolveId hook
// withCommonjsIgnoreBuiltins(config)
} else {
// External builtins has higher priority
withExternalBuiltins(config)

// Users should build App using `cjs`
// TODO: config.build.rollupOptions.output.format = 'cjs'
}

config.define = {
...config.define,
// @see - https://github.com/vitejs/vite/blob/v5.0.11/packages/vite/src/node/plugins/define.ts#L20
'process.env': 'process.env',
}

commonjsPluginAdapter(config)
},
resolveId(source) {
if (builtins.includes(source)) {
return requireAvailable
? prefix + source
: { external: true, id: source } // import
}
},
load(id) {
if (id.startsWith(prefix)) {
const name = id.replace(prefix, '')

if (electronModules.includes(name)) {
return electronRenderer
return electronRendererCjs
}
if (nodeModules.includes(id)) {
if (nodeModules.includes(name)) {
const snippets = libEsm({
require: id,
exports: Object.keys(require(id))
require: name,
exports: Object.keys(require(name))
})

if (alwaysAvailableModules.includes(id)) {
return `${snippets.require}\n${snippets.exports}`
}
return `${getNodeIntegrationEnabledGuard(id)}\n${snippets.require}\n${snippets.exports}`
const importStatement = `const _M_ = require("${name}");`
return alwaysAvailableModules.includes(name)
? `${importStatement}\n${snippets.exports}`
: `${getNodeIntegrationEnabledGuard(name)}\n${importStatement}\n${snippets.exports}`
}
}
},
Expand Down
52 changes: 48 additions & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import fs from 'node:fs'
import path from 'node:path'
import { builtinModules } from 'node:module'
import type { InlineConfig } from 'vite'

Expand All @@ -15,7 +17,7 @@ export const builtins = [
...nodeModules,
]
export const prefix = '\0vite-plugin-electron-preload:'
export const electronRenderer = `
export const electronRendererCjs = `
const electron = require("electron");
export { electron as default };
export const clipboard = electron.clipboard;
Expand All @@ -35,13 +37,13 @@ export const alwaysAvailableModules = [

export function getNodeIntegrationEnabledGuard(name: string) {
return `
if (typeof process.kill !== "function") {
throw new Error(\`${name} is the Node.js module, please enable "nodeIntegration: true" in the main process.\`);
if (process.sandboxed) {
throw new Error(\`${name} is the Node.js module, please set "nodeIntegration: true" in the main process.\`);
}
`
}

export function commonjsPluginAdapter(config: InlineConfig, modules = builtins) {
export function withCommonjsIgnoreBuiltins(config: InlineConfig, modules = builtins) {
config.build ??= {}
config.build.commonjsOptions ??= {}
if (config.build.commonjsOptions.ignore) {
Expand All @@ -60,4 +62,46 @@ export function commonjsPluginAdapter(config: InlineConfig, modules = builtins)
} else {
config.build.commonjsOptions.ignore = modules
}

return config
}

export function withExternalBuiltins(config: InlineConfig, modules = builtins) {
config.build ??= {}
config.build.rollupOptions ??= {}

let external = config.build.rollupOptions.external
if (
Array.isArray(external) ||
typeof external === 'string' ||
external instanceof RegExp
) {
external = modules.concat(external as string[])
} else if (typeof external === 'function') {
const original = external
external = function (source, importer, isResolved) {
if (modules.includes(source)) {
return true
}
return original(source, importer, isResolved)
}
} else {
external = modules
}
config.build.rollupOptions.external = external

return config
}

export async function resolvePackageJson(root = process.cwd()): Promise<{
type?: 'module' | 'commonjs'
[key: string]: any
} | null> {
const packageJsonPath = path.join(root, 'package.json')
const packageJsonStr = await fs.promises.readFile(packageJsonPath, 'utf8')
try {
return JSON.parse(packageJsonStr)
} catch {
return null
}
}
5 changes: 5 additions & 0 deletions test/__snapshots__/commonjs/preload.sandbox-false.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";
const electron = require("electron");
const fs = require("node:fs");
const timers = require("timers");
console.log(electron, fs, timers);
5 changes: 5 additions & 0 deletions test/__snapshots__/commonjs/preload.sandbox.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";
const electron = require("electron");
const fs = require("node:fs");
const timers = require("timers");
console.log(electron, fs, timers);
4 changes: 4 additions & 0 deletions test/__snapshots__/module/preload.sandbox-false.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import electron from "electron";
import fs from "node:fs";
import timers from "timers";
console.log(electron, fs, timers);
Loading

0 comments on commit 39ccef8

Please sign in to comment.