上篇教程我们介绍了如何通过官方提供的Samples来快速跑出来一个hello world插件面板,这样虽然可以让我们快速启动开发,直接在样例的基础上修改我们需要的代码就可以了。但是官方提供的样例,依然还是非常的简陋,提供的配置大体也就是能把项目跑起来而已,而我们实际的插件开发中还会涉及到文件监听、拷贝、混淆、加密打包等等任务,那我们自己去完成一个脚手架
的搭建就很有必要了。
这篇文章我来介绍一下如何搭建一个脚手架,让我们的插件开发更加的规范化。
1. 技术方案选型
在搭建脚手架之前,我们需要先确定技术方案选型,比如用Vue
还是React
,用Js
还是TS
,用webpack
还是vite
等等。
我之前在开发CEP
插件的时候采用的方案是React
+ Typesciprt
+ Webpack
,有这么一些考虑:
React
是目前最流行的前端框架,生态和相关资源都非常丰富,当然你也会反驳说Vue
也很牛逼呀,我这里不想争论两个框架的优劣,大家可以挑自己喜欢的就行。我更偏向于React
的原因是官方有一个在维护的基于React
的组件库React Spectrum,我当前CEP的插件一直在用,虽然它目前还不支持UXP,但是保不准以后会支持呢?
Typescript
是Js的超级,在类型验证和代码提示方面有非常好的效果,能够大幅减少你代码的出错率,强烈推荐。
Webpack
就不用多说了,它是前端开发中最流行的打包工具,也是目前最成熟的打包工具,它的生态和相关资源也是非常丰富的。
但是这次,我想试试不一样的!
Vite
是一个比较新的打包构建工具,相比流行了许多年的webpack
,在性能和先进性上有较多的优势,我想既然UXP已经是从新开始了,为什么不用一个更新的构建工具呢?
但是我在网上一搜,发现几乎没有任何Vite
+ UXP
的一些案例的文章?
不管三七二十八,我们先来动手试一试!
2. 使用Vite构建UXP项目
第一步,我们通过Vite
提供的基础能力,帮助我们创建一个基于React
+ Typescript
的项目,我们可以用官方提供的模板来完成
1
| npm init vite@latest vite-uxp-panel --template react-ts
|
创建完成之后,根据提示进入到目录,执行
我们就可以通过浏览器看到默认的网页效果了,这个是前端页面开发的第一步骤,并且它给我们提供了一个虚拟server,我们只要改动代码,浏览器页面的内容就会发生变化。
然而,这并不是我们想要的,我们需要的是一个能够在Photoshop中运行的插件,上一个教程我们讲述了UXP插件的目录结构,并且使用UXP Developer Tool
来进行加载此插件,也就是说我们需要每次能够通过vite将代码编译出来,而不是放在内存中让浏览器去刷新。
我们通过修改package.json
添加一个命令来完成此操作,这行命令的意思是,先执行tsc
命令,将Typescript
编译成Javascript
,然后再执行vite build --watch
命令,将Javascript
编译成ESM
,并且监听文件变化,实时编译输出到output当中。
1 2 3 4 5
| { "scripts": { "watch": "tsc && vite build --watch" } }
|
上面这个步骤只是将基础的页面内容编译出来了,但是我们还需要将UXP插件的manifest.json放到编译目录下,这样才能够被UXP Developer Tool
加载到。我们可以通过一些拷贝的插件来完成此操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { viteStaticCopy } from 'vite-plugin-static-copy' export default defineConfig({ plugins: [ viteStaticCopy({ assets: [ { src: 'src/manifest.json', dest: 'manifest.json' } ] }) ] });
|
另外,由于vite默认输出的文件都放在了dist目录下,我这边希望它能够编译出来一个插件id的文件夹,类似这样的结构
1 2 3 4 5
| | dist | --- {插件ID} | | --- | --- | manifest.json | --- | --- | index.html | --- | --- | index.js
|
我们继续修改vite.config.js
,添加一个build
的配置,来实现这个目的
1 2 3 4 5 6 7 8
| const pluginId = "xxxxxxxx"; export default defineConfig({ build: { outDir: `./dist/${pluginId}` }, });
|
这样我们的插件就编译输出来,可以通过UXP Developer Tool
进行加载。不过加载之后我们会发现页面是空白的?
查看debug发现引用的Js是通过module
的形式加载的,但是这个JS文件并没有被加载进来。
查了一下,这是因为Vite
面向的是现代浏览器,要求宿主环境支持Native ESM,它打包出来的Js文件引用,都是使用type="module"
模式,而我们的UXP
本身就不是一个正规的浏览器环境,不支持Native ESM,就无法使用module形式加载JS
那怎么办呢?我们需要做的是,将打包出来的Js文件,改成type="text/javascript"
,这样就可以正常加载了。由于Vite
默认就不支持输出text/javascript
了,我们只能通过一个插件来实现这个功能。
1 2 3 4 5 6 7 8 9 10
| import legacy from '@vitejs/plugin-legacy' export default defineConfig({ plugins: [ legacy({ targets: ['defaults', 'not IE 11'], }), ] });
|
这个是官方提供的面向老旧浏览器
的降级方案,这样我们编译出来的内容就可以正常被UXP
加载了,我们重新Load插件,就可以看到一个熟悉的界面。
但是呢,我又发现了问题,当使用plugin-legacy
插件之后,之前配置的vite build --watch
不好使了。使用监听会编译报错,并且会自动删掉index.html
文件!!!!
整个人都不好了!没办法,继续Google之,在官方的issue里头找到许多同样的反馈。然而,并没有人提供解决办法:(,看起来更像是legacy插件的bug,官方看起来也不想管这个插件了……
没有watch
功能的话,我们插件开发起来肯定是效率不行的,不能忍,那怎么办呢?
我们迂回前进!既然默认的vite build --watch
有问题,那我不用你的watch还不行?我单独弄一个watch!我们可以使用单纯第三方node-watch
来完成文件监听的操作。当发生文件变化的时候,调用vite build
,就可以正常输出了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var watch = require('node-watch'); const { exec } = require("child_process");
watch('./', { recursive: true, filter: f => !/node_modules|dist/.test(f) }, function(evt, name) { console.log('%s changed.', name); exec("npm run build", (error, stdout, stderr) => { if (error) { console.error(error); return; } if (stderr) { console.error(error); return; } console.log(stdout); }); });
|
在把这个watch命令配置到package.json
当中
1 2 3 4 5 6 7 8
| { "scripts": { "dev": "vite", "watch": "node watch.js", "build": "tsc && vite build", "preview": "vite preview" } }
|
最后,整体的配置文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { viteStaticCopy } from 'vite-plugin-static-copy' import legacy from '@vitejs/plugin-legacy'
const pluginId = "11aa22bb33";
export default defineConfig({ build: { outDir: `./dist/${pluginId}` }, base: "./", rollupOptions: { output: { esModule: false, preserveModules: false, format: "cjs", } }, plugins: [ react(), legacy({ targets: ['defaults', 'not IE 11'], }), viteStaticCopy({ targets: [ { src: "src/plugin/*", dest: "./" } ] }) ], })
|
至此,在踩了好几个神坑之后,我总算将Vite
+ React
+ TS
的开发环境配置好了,能够正常撸代码,插件也能做到实时刷新。
但是总体看下来,你会发现这里面的方案都非常得挫,一方面要使用plugin-legacy
来做编译降级,这个东西就很明显属于即将被时代淘汰的玩意,同时还没法用默认的watch
,自己弄的node-watch
虽然能跑,但总归感觉不是亲生的娃那样心里隔应。
嗯,大概就到这里,看来我不配用Vite
这种高级玩意,大家看看自己是否需要吧~~
3. 使用Webpack构建UXP项目
这就来到咱主战场了,过程非常简单,几个步骤完事:
- 安装相关依赖库
1 2
| yarn add react react-dom @types/react-dom @types/react yarn add -D webpack webpack-cli url-loader css-loader ts-loader json-loader html-webpack-plugin copy-webpack-plugin
|
- 配置
tsconfig.json
文件
1 2 3 4 5 6 7 8 9 10 11 12 13
| { "compilerOptions": { "target": "es5", "module": "es2015", "jsx": "react", "skipLibCheck": true, "noEmit": false, "noEmitOnError": false, "moduleResolution": "node", "resolveJsonModule": true, "allowSyntheticDefaultImports": true } }
|
- 配置
webpacke.config.js
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| const HtmlWebpackPlugin = require('html-webpack-plugin') const copyWebpackPlugin = require('copy-webpack-plugin'); const webpack = require('webpack'); const path = require("path");
const panelName = `b800bf58`; const dist = path.join(__dirname, 'dist');
function createConfig(mode, entry, output, plugins) { return { entry, module: { rules: [ { test: /\.tsx?$/, exclude: /node_modules/, use: [ { loader: 'ts-loader', options: { transpileOnly: true, configFile: "tsconfig.json" } }], }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.(png|jpg|gif|webp|svg|zip|otf)$/, use: ['url-loader'] }, ], },
resolve: { extensions: ['.tsx', '.ts', '.jsx', '.js', '.json'] }, externals: { _require: "require", photoshop: 'commonjs2 photoshop', uxp: 'commonjs2 uxp', os: 'commonjs2 os', }, output: { filename: '[name].js', path: output },
plugins, } }
module.exports = (env, argv) => { const panelOutput = path.join(dist, `${panelName}.unsigned`); const uxpPanelConfig = createConfig(argv.mode, { uxp: "./src/index.tsx" }, path.join(dist, panelName), [ new webpack.ProvidePlugin({ _require: "_require" }), new HtmlWebpackPlugin({ template: './src/index.html', filename: 'index.html', chunks: ['uxp'], }), new copyWebpackPlugin({ patterns: [ { from: "./manifest.json", to: "." }, { from: "./src/assets/icons", to: "./icons" }, ] }), { apply: (compiler) => { compiler.hooks.afterEmit.tap('AfterEmitPlugin', async (compilation) => { }); } } ]); return [uxpPanelConfig]; }
|
就完事了,可用webpack --watch
来实现代码监听编译。
详细的工程示例在下面,大家自行取用。
4. 总结
这篇文章介绍如何来搭建自己的插件开发脚手架,方案的选型不是唯一的,你可以按照自己的喜好去挑选和配置,相比与CEP
而言,UXP
由于不需要引入JSX
,在编译构建上更偏向web也更简单一些。
我相信有许多小伙伴自己都试过去搭建各种技术方案的脚手架,也欢迎你在评论区分享你的经验,如果需要交流和讨论,可以加入这个微信群。
下一篇,我们就要开始真正干活了,开始上手撸插件的界面和功能,敬请期待。