返回

解决Vue+Electron打包后"Cannot use import"错误

vue.js

Vue-Cli, Electron, Electron-builder 构建应用出现 "Cannot use import statement outside a module" 错误的解决方案

打包好的 Electron 应用运行时报错 "Cannot use import statement outside a module", 开发环境(npm run electron:serve)却一切正常, 这确实让人头大。别急,咱们一起来捋一捋。

问题原因分析

这个问题通常是因为打包后的代码没有被正确地转换为可以在 Node.js 环境下运行的 CommonJS 模块格式。 尽管你在开发环境 (npm run electron:serve)下能跑通,那是因为 Vue CLI 和 webpack-dev-server 在背后帮你做了模块转换的工作。electron-builder 默认情况下可能没有完全按照你期望的方式处理模块。

几个可能的原因:

  1. package.json 中的 main 字段: 指向的文件可能仍然使用了 ES 模块语法 (import/export)。
  2. TypeScript 配置问题: tsconfig.json 中的 module 选项可能设置不当,导致生成的JS文件使用了 import。
  3. Electron-builder 配置问题: electron-builder 可能没有正确地将你的代码转译为 CommonJS 格式。
  4. 第三方库: 引入的一些第三方库可能本身就只提供了 ES 模块格式。

解决方案

下面分条列出可行的解决方案, 逐步排查和解决这个问题.

1. 调整 background.ts (主进程文件)

因为错误信息提示 "Cannot use import statement outside a module",这表示你的 background.ts (或者 package.jsonmain 字段指定的文件)直接或者间接使用了 ES 模块的 import 语句,而 Electron 的主进程在打包后默认运行在 Node.js 环境,它期望的是 CommonJS 模块 (使用 require)。

  • 检查 :仔细检查你的 background.ts 以及它引入的其他文件,确保代码使用require引入, 如果引入的文件很多,建议修改 tsconfig.json 中的 module 配置.
  • 安全建议: 即使修改了模块引入方式,Electron 的安全最佳实践仍然适用(例如,启用 context isolation)。

2. 修改 tsconfig.json 中的 module

tsconfig.json 决定了 TypeScript 如何编译你的代码。 打包后的 Electron 应用需要 CommonJS 模块,而esnext值会让ts保留ES的import语法。

  • 修改:tsconfig.json 文件中的 compilerOptions.module 的值从 esnext 改为 commonjs

    {
      "compilerOptions": {
        // ... 其他配置 ...
        "target": "es6", //或者其他目标
        "module": "commonjs",
        // ... 其他配置 ...
      }
    }
    
  • 重新构建: 修改完 tsconfig.json 后,重新构建应用:

    npm run electron:build
    

3. 处理 preload.js (预加载脚本)

如果你的应用使用了 preload.js 文件来桥接渲染进程和主进程,也需要确保 preload.js 以及它引用的文件符合 CommonJS 规范。

  • 检查: 如同处理 background.ts 一样,使用 require 来引入模块,如果引入了TS编写的代码,则依赖上面tsconfig.json的配置.

4. Electron-builder 配置 (进阶)

如果上述方法都不起作用,可能需要在 electron-builder 的配置文件(通常是 package.json 中的 build 字段)中进行更细致的配置。

  • 使用 afterSign Hook (高级): 在某些特殊情况下,例如使用了特殊的打包方式或者自签名的,可以尝试增加afterSign hook. 在 electron-builder 打包过程的签名后阶段进行额外的处理。
{
  "build": {
     "afterSign": "./afterSignHook.js",
    // ... 其他配置 ...
  }
}

创建afterSignHook.js

    exports.default = async function (context) {
    //  你的自定义处理逻辑, 例如检查,修复
    console.log("afterSign hook executed", context);
    };

  • 调整 asar 配置 (高级): Electron 使用 asar 格式来打包应用程序。你可以尝试关闭 asar 打包,或者更精细地控制 asar 的打包行为。

    • 关闭 asar:package.jsonbuild 字段中设置 asar: false。 这会把你的应用代码以文件形式直接放在 resources 目录下, 方便调试。

      {
          "build":{
              "asar": false
          }
      }
      
    • 使用 asarUnpack: 如果你希望某些文件或文件夹不被打包进 asar 文件,可以使用 asarUnpack 选项。这通常用于包含原生模块或需要在运行时访问的文件。

      {
          "build":{
              "asarUnpack": [
              "node_modules/some-native-module",
              "src/some-external-resource"
            ]
          }
      }
      
  • 尝试不同的构建目标 如果 portable目标不能正确工作,可以更换其他目标:

 "win": {
      "target": [
        "nsis" // or "msi", "zip", etc.
      ]
    }

5. 第三方模块的处理

如果你使用了只提供 ES 模块版本的第三方库,那么可能需要一些额外的处理。

  • 查找 CommonJS 版本: 很多流行的库都会同时提供 ES 模块和 CommonJS 版本。优先使用 CommonJS 版本.
  • 使用构建工具进行转换 (Webpack/Rollup,进阶): 你可以配置 Webpack 或 Rollup 这样的构建工具,让它们在打包 Electron 应用前将这些 ES 模块库转换为 CommonJS 格式. 这个涉及额外的构建步骤, 如果上面的手段不能处理, 可以考虑手动引入Webpack或者rollup.
  • 降级electron版本(不推荐): 如果使用的某些库的版本需要很高的node和electron版本才能使用,并且已经无法更新或者降级库版本, 只能尝试将electron的版本调低。
  • 替换: 使用其他提供CommonJs版本的第三方库.

6.完整的示例(假设已经调整background.ts)

这里只给出tsconfig.jsonpackage.json:

//tsconfig.json
{
    "compilerOptions": {
      "target": "es6",
      "module": "commonjs",
      "strict": true,
      "jsx": "preserve",
      "importHelpers": true,
      "moduleResolution": "node",
      "skipLibCheck": true,
      "esModuleInterop": true,
      "allowSyntheticDefaultImports": true,
      "forceConsistentCasingInFileNames": true,
      "useDefineForClassFields": true,
      "sourceMap": true,
      "baseUrl": ".",
      "types": [
        "webpack-env"
      ],
      "paths": {
        "@/*": [
          "src/*"
        ]
      },
      "lib": [
        "esnext",
        "dom",
        "dom.iterable",
        "scripthost"
      ]
    },
    "include": [
      "src/**/*.ts",
      "src/**/*.tsx",
      "src/**/*.vue",
      "tests/**/*.ts",
      "tests/**/*.tsx"
  , "src/utils/storage.js"  ],
    "exclude": [
      "node_modules",
      "**/*.spec.ts"
    ]
  }
// package.json
{
    "build": {
      "appId": "com.nyyj.frontend",
      "copyright": "KW",
      "author": "Kevin",
      "productName":"南雍易记",
      "icon": "src/assets/icon.png",
      "electronDownload": {
        "mirror": "https://npmmirror.com/mirrors/electron/"
      },
      "win": {
        "target": [
          "portable" //或 "nsis"
        ]
      },
       "asar": false
    },
    "name": "nyyj-frontend",
    "version": "0.1.0",
    "private": true,
    "scripts": {
      "serve": "vue-cli-service serve",
      "build": "vue-cli-service build",
      "lint": "vue-cli-service lint",
      "dev": "vue-cli-service electron:serve",
      "output": "npx electron-builder",
      "electron:build": "vue-cli-service electron:build",
      "electron:serve": "vue-cli-service electron:serve",
      "postinstall": "electron-builder install-app-deps",
      "postuninstall": "electron-builder install-app-deps"
    },
    "main": "src/background.ts", //注意此文件
    "dependencies": {
      "@mdi/font": "5.9.55",
      "roboto-fontface": "*",
      "ts-loader": "~8.2.0",
      "vue": "^3.2.13",
      "vue-router": "^4.0.3",
      "vuetify": "^3.0.0-beta.0",
      "vuex": "^4.0.0",
      "webfontloader": "^1.0.0"
    },
    "devDependencies": {
      "@types/electron-devtools-installer": "^2.2.0",
      "@types/webfontloader": "^1.0.0",
      "@typescript-eslint/eslint-plugin": "^5.4.0",
      "@typescript-eslint/parser": "^5.4.0",
      "@vue/cli-plugin-eslint": "~5.0.0",
      "@vue/cli-plugin-router": "~5.0.0",
      "@vue/cli-plugin-typescript": "~5.0.0",
      "@vue/cli-plugin-vuex": "~5.0.0",
      "@vue/cli-service": "~5.0.0",
      "@vue/eslint-config-typescript": "^9.1.0",
      "electron": "^33.2.0",
      "electron-devtools-installer": "^3.1.0",
      "electron-packager": "^17.1.2",
      "eslint": "^7.32.0",
      "eslint-plugin-vue": "^8.0.3",
      "typescript": "~4.5.5",
      "vue-cli-plugin-electron-builder": "~2.1.1",
      "vue-cli-plugin-vuetify": "~2.5.8",
      "webpack": "^5.75.0",
      "webpack-cli": "^5.1.4",
      "webpack-plugin-vuetify": "^2.0.0-alpha.0"
    },
    "eslintConfig": {
      "root": true,
      "env": {
        "node": true
      },
      "extends": [
        "plugin:vue/vue3-essential",
        "eslint:recommended",
        "@vue/typescript/recommended"
      ],
      "parserOptions": {
        "ecmaVersion": 2020
      },
      "rules": {}
    },
    "browserslist": [
      "> 1%",
      "last 2 versions",
      "not dead",
      "not ie 11"
    ]
  }

按上述方法调整完,大概率就能解决问题。如果还不行,再根据具体的错误信息,重复上述步骤进一步排查. 一定要注意错误信息的提示,结合错误信息反复调整和尝试。