├── .env.example
├── .gitattributes
├── .github
└── FUNDING.yml
├── .gitignore
├── .vscode
├── extensions.json
└── launch.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── scripts
├── binary.js
├── build.js
├── jsxbin.js
└── text.js
├── src
├── icons
│ ├── icon.jpg
│ └── icon.png
├── main.js
└── modules
│ ├── expression.js
│ └── utils.js
└── static
└── README.html
/.env.example:
--------------------------------------------------------------------------------
1 | IM_IN_ENV=Env says hello
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: ["https://www.paypal.com/donate/?hosted_button_id=RGCDAUP9P2DNQ"]
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | dist
4 | .DS_Store
5 | .serve
6 | .env
7 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
4 |
5 | // List of extensions which should be recommended for users of this workspace.
6 | "recommendations": [
7 | "adobe.extendscript-debug"
8 | ],
9 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
10 | "unwantedRecommendations": []
11 | }
12 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Launch Extendscript",
9 | "type": "extendscript-debug",
10 | "request": "launch",
11 | "script": "${workspaceFolder}/build/extender.jsx",
12 | // NOTE: Enable `hostAppSpecifier` to avoid being prompted
13 | // "hostAppSpecifier": "aftereffects-22.0",
14 | },
15 | ],
16 | }
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | - `Added` for new features
9 | - `Changed` for changes in existing functionality
10 | - `Deprecated` for soon-to-be removed features
11 | - `Removed` for now removed features
12 | - `Fixed` for any bug fixes
13 | - `Security` in case of vulnerabilities
14 |
15 | ## [0.0.10] - 2022-12-23
16 |
17 | ### Changed
18 |
19 | - Import as text with `?text` suffix instead of `.text.js`
20 |
21 | ## [0.0.9] - 2022-11-22
22 |
23 | ### Added
24 |
25 | - References to 'Types for Adobe' types
26 |
27 | ### Fixed
28 |
29 | - Cross platform npm scripts
30 | - Error handling in `jsxbin.js`
31 | - Script path in `launch.json`
32 |
33 | ## [0.0.8] - 2022-11-14
34 |
35 | ### Added
36 |
37 | - Import `.png` and `.jpg` icons as binary string
38 |
39 | ## [0.0.7] - 2022-11-08
40 |
41 | ### Added
42 |
43 | - Import JavaScript files as text
44 |
45 | ## [0.0.6] - 2022-08-18
46 |
47 | ### Added
48 |
49 | - Minimum required Node version as `engines` in `package.json`
50 | - Every `.js` file in `src/` is considered an entrypoint
51 | - Multiple entrypoints result in multiple scripts
52 | - A single entrypoint is renamed to `name` from `package.json`
53 |
54 | ### Changed
55 |
56 | - Minification does not rewrite syntax to avoid Extendscript errors
57 |
58 | ## [0.0.5] - 2022-08-12
59 |
60 | ### Added
61 |
62 | - Exposes `PRODUCT_DISPLAY_NAME` environment variable
63 | - Copies static files from `/static`
64 |
65 | ### Changed
66 |
67 | - Renamed entrypoint to `main.js` instead of `app.js`
68 |
69 | ### Fixed
70 |
71 | - Improves debug configuration
72 |
73 | ## [0.0.1] - 2022-08-11
74 |
75 | Initial "release"
76 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Remco Janssen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # extender
2 |
3 | A modern starter for writing Adobe Extendscript
4 |
5 | _(yes, another one)_
6 |
7 | ## Why?
8 |
9 | Writing Extendscript (Ecmascript 3) is rather annoying once you're used to modern Javascript. You expect to use array methods and common practices such as `.env` files, no-nonsense bundling, rebuilding on changes, etc.
10 |
11 | Other starters don't actually transform modern Javascript, so you have to write helper functions for your beloved array methods which totally defeats the point of writing modern Javascript. You might polyfill them, but that pollutes the global scope _(which is bad for everyone if you found a shitty implementation on Stack Overflow)._
12 |
13 | ## Features
14 |
15 | - Modern Javascript with [ponyfills](https://github.com/sindresorhus/ponyfill#how-are-ponyfills-better-than-polyfills) (see [babel-preset-extendscript](https://github.com/fusepilot/babel-preset-extendscript))
16 | - Ecmascript modules for importing and exporting
17 | - Fast bundling with [esbuild](https://github.com/evanw/esbuild) _(TODO: insert obligatory lightning bolt emoji)_
18 | - Rebundles on file changes
19 | - Minifies the release version
20 | - Converts to binary with [extendscript-debugger](https://marketplace.visualstudio.com/items?itemName=Adobe.extendscript-debug)
21 | - Wraps bundle in an [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) to avoid global variables
22 | - Exposes environment variables to Javascript files
23 | - Includes JSON automatically as a ponyfill
24 | - Copies static files from `/static` (with [esbuild-copy-static-files](https://github.com/nickjj/esbuild-copy-static-files))
25 | - Imports `?text` suffixed paths as strings
26 | - Imports icons as binary strings
27 |
28 | ## Try the example
29 |
30 | 1. Duplicate `.env.example` and remove the `.example` extension
31 | 1. Run `npm install && npm start` in your terminal
32 | 1. Run `/build/extender.jsx` in your Adobe app of choice
33 |
34 | ## Development
35 |
36 | ```
37 | npm install && npm start
38 | ```
39 |
40 | This will start watching your source files and builds into the `build` folder.
41 |
42 | ## Release
43 |
44 | ```
45 | npm run release
46 | ```
47 |
48 | This will bundle, minify and jsxbin your source files into the `dist` folder.
49 |
50 | ## Environment Variables
51 |
52 | All variables are replaced by their values upon bundling.
53 |
54 | By default the bundler exposes:
55 |
56 | - `DEVMODE` when `NODE_ENV` is `development` or not,
57 | - `PRODUCT_NAME` which is `name` from `package.json`,
58 | - `PRODUCT_DISPLAY_NAME` which is `displayName` from `package.json` and
59 | - `PRODUCT_VERSION` which is `version` from `package.json`
60 |
61 | If you have a `.env` file it will automatically expose the variables by their name to all Javascript files.
62 |
63 | ## Entrypoints
64 |
65 | Every `.js` file in the root of the `source/` folder is considered an entrypoint. Multiple entrypoints will result in multiple scripts being bundled separately, keeping the same name as their entrypoint. If there's a single entrypoint it will be renamed to `name` from `package.json`.
66 |
67 | ## Debugging
68 |
69 | Press `F5` in VSCode to launch the debugger and you will be prompted for the host application. To avoid the prompt set the `hostAppSpecifier` in `launch.json`
70 |
71 | You can't use breakpoints in your source files, because the [Extendscript Debugger](https://marketplace.visualstudio.com/items?itemName=Adobe.extendscript-debug) doesn't support source maps. Instead, use a `debugger` statement in your source files or set breakpoints in the bundled file (`/build/{PRODUCT_NAME}.jsx`).
72 |
73 | ## Import JavaScript as String
74 |
75 | To import JavaScript files as strings you can suffix the path with `?text` and import them with a custom default name. This is useful when you have snippets that you want to import as strings. Having them as separate files allows you to format and lint them separately, use TypeScript definitions on them, etc. Note that they will be imported as-is and don't get transpiled. This works for any other text based files.
76 |
77 | See [src/main.js](./src/main.js#L6)
78 |
79 | ## Import Icons as Binary String
80 |
81 | Using icons in [scriptUI](https://extendscript.docsforadobe.dev/user-interface-tools/) is easiest when you add them to your source code. To do this you can import the icons directly as `.png` or `.jpg`. They are then transformed to a binary string that can be read by [`File.decode()`](https://extendscript.docsforadobe.dev/file-system-access/file-object.html#decode).
82 |
83 | See [src/main.js](./src/main.js#L17)
84 |
85 | ## Import Node Modules
86 |
87 | You can import Node modules by simply using `import xyz from 'xyz'`. Note that they can't contain browser or Node APIs. Also note that quite some modern Javascript **is not yet** ponyfilled by [babel-preset-extendscript](https://github.com/fusepilot/babel-preset-extendscript#features).
88 |
89 | ## Static Files
90 |
91 | The contents of `/static` will be copied to the `outdir` whenever you run the bundler. This is useful for icons, readme files, etc. Note that any changes in this folder will not be watched, so you need to run the bundler again or save a change in your source files.
92 |
93 | ## Minification
94 |
95 | Only whitespaces are removed and variable names are shortened. The syntax remains intact to avoid Extendscript errors.
96 |
97 | ## Types for Adobe
98 |
99 | [Types for Adobe](https://github.com/aenhancers/Types-for-Adobe) is part of the package, so you can simply use [triple-slash directives](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html) to get the type definitions for your target app. Or create a [`tsconfig.json`](https://github.com/hyperbrew/bolt-cep/blob/master/src/jsx/tsconfig.json) to define which types to use in the whole project.
100 |
101 | See [src/main.js](./src/main.js#L1-L2)
102 |
103 | ## Typescript
104 |
105 | You should even be able to [make it work with Typescript](https://esbuild.github.io/content-types/#typescript) if that's your thing.
106 |
107 | ## By the way
108 |
109 | You can still write regular old Extendscript without the modern syntax.
110 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "extender",
3 | "displayName": "Extender",
4 | "version": "0.0.10",
5 | "type": "module",
6 | "private": true,
7 | "engines": {
8 | "node": ">=16.7.0"
9 | },
10 | "scripts": {
11 | "start": "cross-env NODE_ENV=development npm-run-all build",
12 | "release": "cross-env NODE_ENV=production run-s build jsxbin",
13 | "jsxbin": "node ./scripts/jsxbin.js",
14 | "build": "node ./scripts/build.js"
15 | },
16 | "devDependencies": {
17 | "babel-preset-extendscript": "^1.0.2",
18 | "cross-env": "^7.0.3",
19 | "dotenv": "^16.0.1",
20 | "esbuild": "^0.14.10",
21 | "esbuild-copy-static-files": "^0.1.0",
22 | "esbuild-plugin-babel": "^0.2.3",
23 | "fs-extra": "^10.0.0",
24 | "glob": "^8.0.3",
25 | "just-merge": "^3.1.1",
26 | "npm-run-all": "^4.1.5",
27 | "prettier": "^2.5.1",
28 | "readdirp": "^3.6.0",
29 | "types-for-adobe": "^7.0.7"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/scripts/binary.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs/promises'
2 |
3 | export default function binaryString() {
4 | return {
5 | name: 'binary',
6 | setup(build) {
7 | build.onLoad({ filter: /\.png|jpg$/ }, async (args) => {
8 | const filePath = args.path
9 | const data = await fs.readFile(filePath)
10 | const bin = data.toString('binary')
11 | return {
12 | contents: encodeURIComponent(bin),
13 | loader: 'text'
14 | }
15 | })
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | import copyStaticFiles from 'esbuild-copy-static-files'
2 | import babel from 'esbuild-plugin-babel'
3 | import binaryString from './binary.js'
4 | import textLoader from './text.js'
5 | import { build } from 'esbuild'
6 | import { join } from 'path'
7 | import fs from 'fs-extra'
8 | import glob from 'glob'
9 | import 'dotenv/config'
10 |
11 | const entryPoints = glob.sync('src/*.js')
12 | const devmode = process.env.NODE_ENV === 'development'
13 | const outdir = devmode ? 'build' : 'dist'
14 | const pkg = await fs.readJson('./package.json')
15 | const out = entryPoints.length === 1 ? { outfile: join(outdir, `${pkg.name}.jsx`) } : { outdir }
16 | const define = {
17 | 'DEVMODE': devmode,
18 | 'PRODUCT_NAME': JSON.stringify(pkg.name),
19 | 'PRODUCT_DISPLAY_NAME': JSON.stringify(pkg.displayName),
20 | 'PRODUCT_VERSION': JSON.stringify(pkg.version),
21 | }
22 |
23 | for (const key in process.env) {
24 | const invalid = key.includes('(x86)')
25 | if (!invalid) {
26 | define[key] = JSON.stringify(process.env[key])
27 | }
28 | }
29 |
30 | build({
31 | ...out,
32 | define,
33 | entryPoints,
34 | logLevel: 'info',
35 | bundle: true,
36 | sourcemap: devmode,
37 | target: ['es5'],
38 | minifyWhitespace: !devmode,
39 | minifyIdentifiers: !devmode,
40 | outExtension: { '.js': '.jsx' },
41 | plugins: [
42 | copyStaticFiles({ dest: outdir }),
43 | binaryString(),
44 | textLoader(),
45 | babel({
46 | config: {
47 | presets: [
48 | ['extendscript', { modules: false }]
49 | ]
50 | }
51 | }),
52 | ],
53 | watch: devmode && {
54 | onRebuild(error) {
55 | if (error) console.error(error)
56 | },
57 | }
58 | })
59 |
--------------------------------------------------------------------------------
/scripts/jsxbin.js:
--------------------------------------------------------------------------------
1 | import { renameSync, existsSync } from 'fs'
2 | import { readdir } from 'fs/promises'
3 | import { fork } from 'child_process'
4 | import readdirp from 'readdirp'
5 | import { homedir } from 'os'
6 | import path from 'path'
7 |
8 | const devmode = process.env.NODE_ENV === 'development'
9 | const outdir = devmode ? 'build' : 'dist'
10 |
11 | const curDir = path.resolve(outdir)
12 | const foundScripts = await readdirp.promise(curDir, { fileFilter: '*.jsx' })
13 | const scripts = foundScripts.map((f) => f.fullPath)
14 | const exportJSXBin = await getExtensionPath()
15 |
16 | scripts.forEach((script) => {
17 | fork(exportJSXBin, ['-f', '-n', script])
18 | .on('close', () => renameSync(`${script}bin`, script))
19 | })
20 |
21 | async function getExtensionPath() {
22 | const extensionsPath = path.join(homedir(), '.vscode', 'extensions')
23 | if (!existsSync(extensionsPath)) {
24 | throw new Error(`Missing VSCode extensions folder at ${extensionsPath}`)
25 | }
26 | const extensions = await readdir(extensionsPath)
27 | const extensionName = 'adobe.extendscript-debug'
28 | const extendscriptFolder = extensions.find((f) => f.includes(extensionName))
29 | if (!extendscriptFolder) {
30 | throw new Error(`Missing VSCode extension ${path.join(extensionsPath, extensionName)}`)
31 | }
32 | const jsxBinPath = path.join(extensionsPath, extendscriptFolder, 'public-scripts', 'exportToJSXBin.js')
33 | if (!existsSync(jsxBinPath)) {
34 | throw new Error(`Expected script at ${jsxBinPath}`)
35 | }
36 | return jsxBinPath
37 | }
38 |
--------------------------------------------------------------------------------
/scripts/text.js:
--------------------------------------------------------------------------------
1 | import { readFile } from 'fs/promises'
2 | import { join, isAbsolute } from 'path'
3 |
4 | // SOURCE: https://github.com/hannoeru/esbuild-plugin-raw
5 | export default function textLoader() {
6 | const namespace = 'text-loader'
7 | const filter = /\?text$/
8 | return {
9 | name: 'textloader',
10 | setup(build) {
11 | build.onResolve({ filter }, (args) => {
12 | return {
13 | namespace,
14 | path: args.path,
15 | pluginData: {
16 | isAbsolute: isAbsolute(args.path),
17 | resolveDir: args.resolveDir,
18 | },
19 | }
20 | })
21 | build.onLoad({ filter, namespace }, async ({ path, pluginData }) => {
22 | const { resolveDir, isAbsolute } = pluginData
23 | const fullPath = isAbsolute ? path : join(resolveDir, path)
24 | const withoutSuffix = fullPath.replace(filter, '')
25 | return {
26 | contents: await readFile(withoutSuffix),
27 | loader: 'text',
28 | }
29 | })
30 | },
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/icons/icon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Klustre/extender/cae1cd851bcd01a265c64bef0e5c9974e9d32792/src/icons/icon.jpg
--------------------------------------------------------------------------------
/src/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Klustre/extender/cae1cd851bcd01a265c64bef0e5c9974e9d32792/src/icons/icon.png
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | ///
11 | Redirecting to https://supa.supply/ 12 |
13 | 16 | 17 | 18 | --------------------------------------------------------------------------------