magicdawn / magicdawn

个人学习 / 代码 / 总结 / 读书笔记
25 stars 3 forks source link

electron + vite/tsup #179

Open magicdawn opened 2 months ago

magicdawn commented 2 months ago

main process

cjs

使用 tsup build cjs 时比较简单, 需要注意的是 bundle deps except electron

可以使用

// tsup.config.ts
export default defineConfig({
  entry: ['src/index.ts'],
  format: 'cjs',
  platform: 'node',
  noExternal: /.*/, // bundle any dep
  esbuildOptions(options) {
    options.charset = 'utf8'
    options.external ||= []
    options.external.push('electron') // externalize electron
  }
})

注意 noExternal 正则, 与 options.external.push('electron')

esm

electron 支持 esm 了

// tsup.config.ts

import { randomUUID } from 'crypto'
import _ from 'lodash'
import { builtinModules } from 'module'
import path from 'path'
import { defineConfig } from 'tsup'

const { camelCase } = _
const env = process.env.NODE_ENV || 'development'
const REPO_ROOT = path.join(import.meta.dirname, '../../')

const nsp = randomUUID()
const prefix = randomUUID()

export default defineConfig({
  entry: ['./src/index.ts'],
  format: 'esm',
  outExtension: () => ({ js: '.mjs', dts: '.d.mts' }),
  outDir: path.join(REPO_ROOT, `bundle/${env}/main/`),
  clean: true,

  platform: 'node',
  // TODO: get node version based on electron version
  // Using: Node.js v18.15.0 and Electron.js v25.3.0
  target: 'node18',

  env: {
    NODE_ENV: env,
  },

  // NOTE: 此处 external & noExternal 只是输入给 esbuild.options({ plugins: [externalPlugin] })
  // 无法做到, bundle any dep, except `electron`
  // 直接使用 `esbuildOptions.external` 则 esbuild.onResolve / onLoad 逻辑都不会走
  noExternal: [/.*/],

  esbuildOptions(options, context) {
    options.charset = 'utf8'
    options.external ||= []
    // options.external.push('electron')
  },

  esbuildPlugins: [
    // esbuild doesn't transpile `require('foo')` into `import` statements if 'foo' is externalized
    // https://github.com/evanw/esbuild/issues/566#issuecomment-735551834
    // https://github.com/vitejs/vite/blob/main/packages/vite/src/node/optimizer/esbuildDepPlugin.ts#L300
    {
      name: 'custom-external-plugin',
      setup(build) {
        const filter = new RegExp(
          ['electron', ...builtinModules.map((x) => [x, `node\\:${x}`]).flat()]
            .map((id) => `(?:^${id}$)`)
            .join('|'),
        )
        // console.log(filter)

        build.onResolve({ filter }, (args) => {
          if (args.kind === 'require-call') {
            return {
              path: args.path,
              namespace: nsp,
            }
          }
          return {
            path: args.path,
            external: true,
          }
        })

        build.onLoad({ filter: /.*/, namespace: nsp }, (args) => {
          const m = camelCase(args.path).replace(/[:/]/g, '_')
          return {
            contents: `
              import ${m} from ${JSON.stringify(prefix + args.path)};
              module.exports = ${m};
            `,
          }
        })

        build.onResolve({ filter: new RegExp(`^${prefix}`) }, (args) => {
          return {
            path: args.path.slice(prefix.length),
            external: true,
          }
        })
      },
    },
  ],
})

因为 esbuild 如果 external 了 builtin module 如 fs, require('fs') 将会得到保留, 到运行时就会报 dynamic require of fs is not supported. 需要一个 esbuild plugin, 从 vite 中摘的:

最后生成的代码长这样

// bf7bd391-8f37-4e9a-8c3e-8266c1c54f72:electron
import electron2 from "electron";
var require_electron = __commonJS({
  "bf7bd391-8f37-4e9a-8c3e-8266c1c54f72:electron"(exports, module) {
    module.exports = electron2;
  }
});

// bf7bd391-8f37-4e9a-8c3e-8266c1c54f72:fs
import fs2 from "fs";
var require_fs = __commonJS({
  "bf7bd391-8f37-4e9a-8c3e-8266c1c54f72:fs"(exports, module) {
    module.exports = fs2;
  }
});

// ...

[!CAUTION] vite 中使用 namespace import, 如 import * as fs from 'fs'; module.exports = fs; 但实际 namespace import 结果会有兼容问题:

  • fs-extra>graceful-fs 会定义 fs.xxx, 而 namespace import 结果是 freeze 的
  • 有代码在调用 require('constants').hasOwnProperty, namespace import 结果上没有 Object.prototype 上的方法
magicdawn commented 2 months ago

renderer

https://github.com/magicdawn/clash-config-manager/blob/main/packages/ui/vite.config.ts

even in 2024