Skip to content

认识 preload ​

当你在 plugin.json 文件配置了 preload 字段,指定的 js 文件将被预加载,该 js 文件可以调用 Node.js API 的本地原生能力和 Electron 渲染进程 API。

为什么需要 preload ​

在传统的 web 开发中,为了保持用户运行环境的安全,JavaScript 被做了很强的沙箱限制,比如不能访问本地文件,不能访问跨域网络资源,不能访问本地存储等。

uTools 基于 Electron 构建,通过 preload 机制,在渲染线程中,释放了沙箱限制,使得用户可以通过调用 Node.js 的 API 来访问本地文件、跨域网络资源、本地存储等。

preload 的定义 ​

preload 是完全独立于前端项目的一个特殊文件,它应当与 plugin.json 位于同一目录或其子目录下,保证可以在打包插件应用时可以被一起打包。

preload js 文件遵循 CommonJS 规范,因此你可以使用 require 来引入 Node.js 模块,此部分可以参考 Node.js 文档。

前端使用 preload ​

只需给 window 对象自定义一个属性,前端就可直接访问该属性。

js
const fs = require("fs");

window.customApis = {
  readFile: (path) => {
    return fs.readFileSync(path, "utf8");
  },
};
jsx
import { useEffect, useState } from "react";
export default function App() {
  const [file, setFile] = useState("");
  useEffect(() => {
    window.customApis.readFile("/path/to/README.md").then((data) => {
      setFile(data);
    }
  }, []);

  return (
    <div>
      <pre>{file}</pre>
    <div>
  )
}

preload js 规范 ​

由于 preload js 文件可使用本地原生能力,为了防止开发者滥用各种读写文件、网络等能力,uTools 规定:

  • preload js 文件代码不能进行打包/压缩/混淆等操作,要保证每一行代码清晰可读。
  • 引入的第三方模块也必须清晰可读,在提交时将源码一同提交,同样不允许压缩/混淆。

使用 Node.js ​

preload js 文件遵循 CommonJS 规范,通过 require 引入 Node.js (16.x 版本) 模块

可以引入 Node.js 所有原生模块,开发者自己编写的 Node.js 模块以及第三方 Node.js 模块。

引入 Node.js 原生模块 ​

js
const fs = require("node:fs");
const path = require("node:path");
const os = require("node:os");
const { execSync } = require("node:child_process");

window.services = {
  readFile: (filename) => {
    return fs.readFileSync(filename, { encoding: "utf-8" });
  },
  getFolder: (filepath) => {
    return path.dirname(filepath);
  },
  getOSInfo: () => {
    return { arch: os.arch(), cpus: os.cpus(), release: os.release() };
  },
  execCommand: (command) => {
    execSync(command);
  },
};

引入自己编写的模块 ​

js
const writeText = require("./libs/writeText.js");

window.services = {
  writeText,
};
js
const fs = require("fs");
const path = require("path");

module.exports = function writeText(text, filePath) {
  const dir = path.dirname(filePath);
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
    fs.writeFileSync(filePath, text);
    return true;
  }
  return false;
};

引入第三方模块 ​

通过 npm 安装 ​

在 preload.js 同级目录下,保证存在一个独立的 package.json,并且设置 type 为 commons。

json
{
  "type": "commonjs"
  "dependencies": {}
}

在 preload.js 同级目录下,执行 npm install 安装第三方模块,保证 node_modules 目录存在。

以下是通过 npm 引入 colord 的示例:

bash
npm install colord
js
const { getFormat, colord } = require("colord");

window.services = {
  colord: {
    darken(text) {
      const fmt = getFormat(text);
      if (!fmt) {
        return [null, "请输入一个有效的颜色值,比如 #000 或 rgb(0,0,0)"];
      } else {
        const darkColor = colord(text).darken(0.1);
        return [darkColor, null];
      }
    },
  },
};

通过源码引入 ​

在 preload.js 同级目录下,下载源码,并使用 require 引入。

比如从 github 下载 nodemailer:

bash
git clone https://github.com/nodemailer/nodemailer.git
js
const nodemailer = require("./nodemailer");
const _setImmediate = setImmediate;
process.once("loaded", function () {
  global.setImmediate = _setImmediate;
});
const sendMail = () => {
  let transporter = require("./nodemailer").createTransport({
    host: "smtp.qq.com",
    port: 465,
    secure: true,
    auth: {
      user: "[email protected]",
      pass: "xxx",
    },
  });
  let mailOptions = {
    from: "[email protected]",
    to: "[email protected]",
    subject: "Sending Email using Node.js",
    text: "That was easy!",
  };

  transporter.sendMail(mailOptions, function (error, info) {
    if (error) {
      console.log(error);
    } else {
      console.log("Email sent: " + info.response);
    }
  });
};
window.services = {
  sendMail: () => {
    return sendMail();
  },
};

引入 Electron 渲染进程 API ​

js
const { clipboard, nativeImage } = require("electron");

window.services = {
  copyImage: (imageFilePath) => {
    clipboard.writeImage(nativeImage.createFromPath(imageFilePath))
  },
};