【UXP教程-6】插件面板实战-运行JSX代码

【UXP教程-6】插件面板实战-运行JSX代码

UXP是Adobe新推出(虽然也有几年了)的插件架构,不像以前CEP那样使用JSX来和宿主进行交互,UXP官方提供了对应的photoshop-api,使用起来融合性更好,更方便一些。但是你在是做CEP插件迁移的时候会发现官方提供的API寥寥无几,很多以前在JSX可以运行的能力都缺失了,虽然Adobe也意识到了这个问题,于是提供了一个叫做batch play的补丁运作机制,但用起来还是非常别扭。

早在两年前多前,我在刚开始试水UXP插件的时候,就想过是不是可以在UXP中执行JSX代码,摸索了好久没找到方法,当时主要是觉得UXP在执行本地文件的时候有安全限制,所以感觉应该没戏了,就放弃了,后来一段时间也没有继续关注插件开发。

前一段时间,群里头有一个小伙伴在研究这个东西,并且找到了途径,在群里头做了分享,我当时也忙没顾上研究,趁这个春节假期比较闲,就打算深入看看,还真找到了方法,在这里先感谢 @糖炒栗子 的分享。

uxp-run-jsx

核心思路

我们的目标是要实现像CEP中那种csInterface.evalScript()要实现这个功能,可以静默执行JSX代码(或文件),要实现这个功能本质需要两个条件:

  1. 能够在UXP中执行JSX的文件
  2. 绕开UXP的系统文件限制

1. 能够在UXP中执行JSX的文件

这个能力,是借助了Ps自带的执行脚本文件的功能,如下

photoshop run script

这个功能在执行的时候,会输出ActionManager的代码,我们可以通过batchPlay来重写这段代码,结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { core, action } from 'photoshop';

async function run(filePath: string) {
const params = {
_obj: "AdobeScriptAutomation Scripts",
javaScript: {
_kind: "local",
_path: filePath,
},
javaScriptMessage: "undefined",
_options: {
dialogOptions: "dontDisplay",
},
};

const result = await core.executeAsModal(async () => {
return await action.batchPlay([params], {
modalBehavior: 'execute',
synchronousExecution:false });
}, "run");
return result;
}

这段代码,你只要传递正确的jsx文件路径,它就能够正确的执行,并且可以返回你JSX的执行结果。(返回值部分我们后面再介绍)

2. 绕开UXP的系统文件限制

这个是之前我困扰我好久的地方,因为UXP的安全限制要求访问系统文件,必须用户进行授权,就是得弹出一个文件选择框让用户选了,你才能操作里头的文件,这也是我之前放弃继续探索的原因,但是前几天我在开发图层导出PNG的时候,在论坛上看到了一些别人的代码,非常神奇,居然可以直接给目标文件自己授权,不通过系统选择框,于是我就将这两者结合起来,实现了这个功能。

1
2
3
4
async function tokenify(filePath: string) {
const entry = await storage.localFileSystem.getEntryWithUrl("file:" + filePath);
return await storage.localFileSystem.createSessionToken(entry);
}

这段代码的功能就是给目标路径创建一个会话的token,相当于给文件授权了,这样batchPlay就会信任这个文件,你甚至都可以直接将token作为filePath传递给run函数,这样就实现了静默执行JSX文件的功能。

其实代码就这么些,没有特别复杂的东西,于是相当于你自己实现了一个csInterface.evalScript(),这样你的uxp插件就会变得和CEP插件类似了,岂不美哉。

3. 返回值获取

如何获取到JSX文件的执行结果呢? 其实JSX文件(代码)的执行结果,会以标准输出的形式出出现,并被batchPlay捕获并且返回,你只要在jsx文件的最后,把返回值写上,就可以获取到了,类似这样:

1
2
3
var a = 1;
var b = 2;
a+b;

最后一行的a+b就是返回值,你只要在run函数中获取到返回值,就可以获取到JSX文件的执行结果了,它会在batchPlay返回的javaScriptMessage中。

uxp-run-jsx3

这只是一个简单的例子,如果你想返回一个复杂的数据对象,可以使用JSON.stringify()来将对象转换为字符串,然后在UXP文件中使用JSON.parse()来解析返回值。

1
2
3
4
var a = 1;
var b = 2;
// 这里记得要自己在jsx文件中引入JSON库
JSON.stringify({a, b, sum: a+b});

综合

有了这些能力之后,你就可以和以往CEP一样开发插件了,你可以把jsx文件打包放到uxp的插件目录里头,然后在需要执行对应文件的时候,使用上面的代码,这里给个示例:

1
2
3
4
5
6
7
8
9
10
11
import { storage, shell } from 'uxp';

async function runJsxFile(filename: string) {
const pluginFolder = await storage.localFileSystem.getPluginFolder();
const file = await pluginFolder.getEntry(filename);
const token = await storage.localFileSystem.createSessionToken(file);
return await run(token);
}

// 执行
runJsxFile("test.jsx");

然后你可以借助这个能力,写一个执行一段jsx代码的函数,你只要把这段代码临时写入到本地沙箱目录就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export async function runJsxCode(jsx: string) {
try {
const folder = await getStorageFolder();
const file = await folder.createFile("run.jsx", { overwrite: true });
await file.write(jsx);
const token = await tokenify(file.nativePath);
return await run(token);
} catch (error) {
console.log(error);
}
}

// 执行
const ret = await runJsxCode('var a = 1; b=2; a+b');
console.log(ret);

这个能力还是出乎我意料的,因为按照这个模式,UXP的安全限制并不完整,意思是你可以自己创建SessionToken来绕过系统文件的授权限制,这就打开了一个更大的空间,能做的事情会更多……

能够执行JSX代码,可以大幅扩展UXP的插件能力,你甚至可以把uxp当做一个壳,核心能力都用jsx来完成,这样从CEP迁移到UXP的成本就会低很多。还有UXP由于有很多安全限制,无法很顺畅的做系统文件操作,你就可以把这些操作都挪到JSX当中,就变相的绕过了这些限制:

安全限制

好了,这篇文章就写到这里,希望对大家有帮助!再次感谢 @糖炒栗子 同学,也希望大家可以多多分享,促进交流和成长。

评论