@swmansion/popcorn 0.3.3 → 0.4.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +12 -0
- package/README.md +87 -2
- package/dist/beam.d.ts +10 -0
- package/dist/errors.d.ts +96 -39
- package/dist/etf.d.ts +38 -0
- package/dist/events.d.ts +82 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.mjs +1313 -2
- package/dist/plugins/esbuild.d.ts +10 -2
- package/dist/plugins/esbuild.mjs +35 -36
- package/dist/plugins/rollup.d.ts +10 -2
- package/dist/plugins/rollup.mjs +40 -25
- package/dist/plugins/shared.d.ts +34 -4
- package/dist/plugins/shared.mjs +61 -0
- package/dist/plugins/vite.d.ts +22 -2
- package/dist/plugins/vite.mjs +201 -78
- package/dist/popcorn.d.ts +241 -110
- package/dist/runtimes/core/beam.emu.mjs +141 -0
- package/dist/runtimes/core/beam.mjs +141 -0
- package/dist/runtimes/core/beam.wasm +0 -0
- package/dist/runtimes/core/manifest.json +1 -0
- package/dist/runtimes/crypto/beam.emu.mjs +520 -0
- package/dist/runtimes/crypto/beam.mjs +520 -0
- package/dist/runtimes/crypto/beam.wasm +0 -0
- package/dist/runtimes/crypto/manifest.json +1 -0
- package/dist/tar.d.ts +4 -0
- package/dist/types.d.ts +114 -63
- package/dist/utils.d.ts +7 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.mjs +826 -0
- package/package.json +20 -28
- package/dist/AtomVM.mjs +0 -7992
- package/dist/AtomVM.wasm +0 -0
- package/dist/bridge.d.ts +0 -22
- package/dist/bridge.mjs +0 -66
- package/dist/errors.mjs +0 -55
- package/dist/iframe.d.ts +0 -1
- package/dist/iframe.mjs +0 -215
- package/dist/popcorn.mjs +0 -381
- package/dist/types.mjs +0 -25
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import type { Plugin } from "esbuild";
|
|
2
|
-
import { type
|
|
3
|
-
|
|
2
|
+
import { type Options } from "./shared";
|
|
3
|
+
/**
|
|
4
|
+
* Copies the worker, VM runtime, and `otp/` assets into the output directory after a successful esbuild build.
|
|
5
|
+
*
|
|
6
|
+
* Requires `format: "esm"` and either `outdir` or `outfile`.
|
|
7
|
+
* If the application bundle is elsewhere, set `PopcornOpts.workerUrl` to the copied `worker.mjs`.
|
|
8
|
+
*
|
|
9
|
+
* @see {@link Options} for application packaging and server requirements.
|
|
10
|
+
*/
|
|
11
|
+
export declare function popcorn(options: Options): Plugin;
|
package/dist/plugins/esbuild.mjs
CHANGED
|
@@ -1,54 +1,53 @@
|
|
|
1
|
-
import { mkdir,
|
|
2
|
-
import { dirname, resolve
|
|
3
|
-
import {
|
|
1
|
+
import { mkdir, cp, rm } from 'node:fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { p as popcorn$1 } from './shared.mjs';
|
|
4
|
+
import 'node:child_process';
|
|
5
|
+
import 'node:os';
|
|
6
|
+
import 'node:util';
|
|
7
|
+
import 'node:url';
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Copies the worker, VM runtime, and `otp/` assets into the output directory after a successful esbuild build.
|
|
11
|
+
*
|
|
12
|
+
* Requires `format: "esm"` and either `outdir` or `outfile`.
|
|
13
|
+
* If the application bundle is elsewhere, set `PopcornOpts.workerUrl` to the copied `worker.mjs`.
|
|
14
|
+
*
|
|
15
|
+
* @see {@link Options} for application packaging and server requirements.
|
|
16
|
+
*/
|
|
8
17
|
function popcorn(options) {
|
|
9
|
-
const bundles = options.bundlePaths.map((p) => ({
|
|
10
|
-
name: basename(p),
|
|
11
|
-
dir: dirname(p),
|
|
12
|
-
}));
|
|
13
18
|
let outputDir;
|
|
14
19
|
return {
|
|
15
|
-
name: "popcorn",
|
|
20
|
+
name: "popcorn-otp",
|
|
16
21
|
setup(build) {
|
|
17
22
|
build.onStart(() => {
|
|
18
23
|
const opts = build.initialOptions;
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
if (outdir === undefined) {
|
|
26
|
-
throw new Error("[popcorn] outdir is not specified, cannot copy files");
|
|
27
|
-
}
|
|
28
|
-
outputDir = outdir;
|
|
24
|
+
const outdir = opts.outdir ??
|
|
25
|
+
(opts.outfile === undefined ? undefined : dirname(opts.outfile));
|
|
26
|
+
assert(opts.format === "esm", "Popcorn OTP works only with esm builds.");
|
|
27
|
+
assert(outdir !== undefined, "outdir is not specified, cannot copy files");
|
|
28
|
+
outputDir = resolve(outdir);
|
|
29
29
|
});
|
|
30
|
-
build.onEnd(async () => {
|
|
31
|
-
|
|
30
|
+
build.onEnd(async (result) => {
|
|
31
|
+
if (result.errors.length > 0)
|
|
32
|
+
return;
|
|
33
|
+
assert(outputDir !== undefined, "outdir was not resolved");
|
|
34
|
+
const outDir = outputDir;
|
|
35
|
+
const prepared = await popcorn$1(options);
|
|
32
36
|
try {
|
|
33
|
-
await
|
|
34
|
-
|
|
35
|
-
...bundles.map((b) => copy(b.name, { inDir: b.dir, outDir: outputDir })),
|
|
36
|
-
// Copy popcorn runtime files to output directory
|
|
37
|
-
// These need to be alongside the bundled code for import.meta.url to work
|
|
38
|
-
copy("iframe.mjs", { inDir: popcornDistDir, outDir: outputDir }),
|
|
39
|
-
copy("AtomVM.mjs", { inDir: popcornDistDir, outDir: outputDir }),
|
|
40
|
-
copy("AtomVM.wasm", { inDir: popcornDistDir, outDir: outputDir }),
|
|
41
|
-
]);
|
|
37
|
+
await mkdir(outDir, { recursive: true });
|
|
38
|
+
await cp(prepared.dir, outDir, { recursive: true });
|
|
42
39
|
}
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
finally {
|
|
41
|
+
await rm(prepared.dir, { recursive: true, force: true });
|
|
45
42
|
}
|
|
46
43
|
});
|
|
47
44
|
},
|
|
48
45
|
};
|
|
49
46
|
}
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
function assert(ok, message) {
|
|
48
|
+
if (!ok) {
|
|
49
|
+
throw new Error(`[popcorn-otp] ${message}`);
|
|
50
|
+
}
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
export { popcorn };
|
package/dist/plugins/rollup.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import type { Plugin } from "rollup";
|
|
2
|
-
import { type
|
|
3
|
-
|
|
2
|
+
import { type Options } from "./shared";
|
|
3
|
+
/**
|
|
4
|
+
* Copies the worker, VM runtime, and `otp/` assets into the output directory after Rollup writes the bundle.
|
|
5
|
+
*
|
|
6
|
+
* Requires `output.format: "es"` and either `output.dir` or `output.file`.
|
|
7
|
+
* If the application bundle is elsewhere, set `PopcornOpts.workerUrl` to the copied `worker.mjs`.
|
|
8
|
+
*
|
|
9
|
+
* @see {@link Options} for application packaging and server requirements.
|
|
10
|
+
*/
|
|
11
|
+
export declare function popcorn(options: Options): Plugin;
|
package/dist/plugins/rollup.mjs
CHANGED
|
@@ -1,35 +1,50 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname, resolve
|
|
3
|
-
import {
|
|
1
|
+
import { mkdir, cp, rm } from 'node:fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { p as popcorn$1 } from './shared.mjs';
|
|
4
|
+
import 'node:child_process';
|
|
5
|
+
import 'node:os';
|
|
6
|
+
import 'node:util';
|
|
7
|
+
import 'node:url';
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Copies the worker, VM runtime, and `otp/` assets into the output directory after Rollup writes the bundle.
|
|
11
|
+
*
|
|
12
|
+
* Requires `output.format: "es"` and either `output.dir` or `output.file`.
|
|
13
|
+
* If the application bundle is elsewhere, set `PopcornOpts.workerUrl` to the copied `worker.mjs`.
|
|
14
|
+
*
|
|
15
|
+
* @see {@link Options} for application packaging and server requirements.
|
|
16
|
+
*/
|
|
8
17
|
function popcorn(options) {
|
|
9
|
-
|
|
10
|
-
path: p,
|
|
11
|
-
name: basename(p),
|
|
12
|
-
}));
|
|
18
|
+
let outputDir;
|
|
13
19
|
return {
|
|
14
|
-
name: "popcorn",
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
fileName: resolve(popcornDistDir, bundle.name),
|
|
21
|
-
source: await readFile(bundle.path),
|
|
22
|
-
});
|
|
20
|
+
name: "popcorn-otp",
|
|
21
|
+
renderStart(outputOptions) {
|
|
22
|
+
assert(outputOptions.format === "es", "Popcorn OTP works only with esm builds.");
|
|
23
|
+
let dir = outputOptions.dir;
|
|
24
|
+
if (dir === undefined && outputOptions.file !== undefined) {
|
|
25
|
+
dir = dirname(outputOptions.file);
|
|
23
26
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
assert(dir !== undefined, "output dir is not specified, cannot copy files");
|
|
28
|
+
outputDir = resolve(dir);
|
|
29
|
+
},
|
|
30
|
+
async writeBundle() {
|
|
31
|
+
assert(outputDir !== undefined, "output dir was not resolved");
|
|
32
|
+
const outDir = outputDir;
|
|
33
|
+
const prepared = await popcorn$1(options);
|
|
34
|
+
try {
|
|
35
|
+
await mkdir(outDir, { recursive: true });
|
|
36
|
+
await cp(prepared.dir, outDir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await rm(prepared.dir, { recursive: true, force: true });
|
|
30
40
|
}
|
|
31
41
|
},
|
|
32
42
|
};
|
|
33
43
|
}
|
|
44
|
+
function assert(ok, message) {
|
|
45
|
+
if (!ok) {
|
|
46
|
+
throw new Error(`[popcorn-otp] ${message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
34
49
|
|
|
35
50
|
export { popcorn };
|
package/dist/plugins/shared.d.ts
CHANGED
|
@@ -1,7 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
type RuntimeVariant = "core" | "crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Shared options for the Vite, Rollup, and esbuild plugins.
|
|
4
|
+
*
|
|
5
|
+
* The plugins invoke `mix popcorn.cook` in the project directory, which
|
|
6
|
+
* compiles the project before packaging it.
|
|
7
|
+
*/
|
|
8
|
+
export type Options = {
|
|
9
|
+
/** Runtime variant override. */
|
|
10
|
+
runtimeVariant?: RuntimeVariant;
|
|
11
|
+
/** Mix project directory. */
|
|
12
|
+
rootDir: string;
|
|
3
13
|
/**
|
|
4
|
-
*
|
|
14
|
+
* OTP application to start after VM boot.
|
|
15
|
+
* Defaults to the current Mix application.
|
|
5
16
|
*/
|
|
6
|
-
|
|
17
|
+
app?: string | null;
|
|
18
|
+
/** Additional applications to package with their dependencies. */
|
|
19
|
+
extraApps?: string[];
|
|
20
|
+
/** Brotli compression effort. Defaults to `standard`. */
|
|
21
|
+
brotliEffort?: "standard" | "max";
|
|
22
|
+
/** Removes nonessential BEAM chunks. Defaults to `true`. */
|
|
23
|
+
strip?: boolean;
|
|
24
|
+
/** Controls removal of unreachable modules and functions. Defaults to `none`. */
|
|
25
|
+
treeshake?: TreeshakeOptions;
|
|
7
26
|
};
|
|
27
|
+
export type TreeshakeOptions = {
|
|
28
|
+
preservedApps?: string[];
|
|
29
|
+
};
|
|
30
|
+
export type Prepared = {
|
|
31
|
+
dir: string;
|
|
32
|
+
runtimeVariant: RuntimeVariant;
|
|
33
|
+
notes: unknown[];
|
|
34
|
+
};
|
|
35
|
+
export declare function popcorn(options: Options): Promise<Prepared>;
|
|
36
|
+
export declare function runtimeDirectory(variant: RuntimeVariant): string;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
3
|
+
import { resolve, dirname } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
async function popcorn(options) {
|
|
10
|
+
const dir = await mkdtemp(`${tmpdir()}/popcorn-otp-`);
|
|
11
|
+
const args = ["popcorn.cook", "--out-dir", dir];
|
|
12
|
+
if (options.app === null)
|
|
13
|
+
args.push("--no-app");
|
|
14
|
+
else if (options.app !== undefined)
|
|
15
|
+
args.push("--app", options.app);
|
|
16
|
+
for (const app of options.extraApps ?? [])
|
|
17
|
+
args.push("--extra-app", app);
|
|
18
|
+
if (options.runtimeVariant !== undefined) {
|
|
19
|
+
args.push("--runtime-variant", options.runtimeVariant);
|
|
20
|
+
}
|
|
21
|
+
if (options.brotliEffort !== undefined) {
|
|
22
|
+
args.push("--brotli-effort", options.brotliEffort);
|
|
23
|
+
}
|
|
24
|
+
if (options.strip === false)
|
|
25
|
+
args.push("--no-strip");
|
|
26
|
+
if (options.treeshake !== undefined) {
|
|
27
|
+
args.push("--treeshake");
|
|
28
|
+
for (const app of options.treeshake.preservedApps ?? []) {
|
|
29
|
+
args.push("--preserved-app", app);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
await execFileAsync("mix", args, {
|
|
34
|
+
cwd: resolve(options.rootDir),
|
|
35
|
+
env: { ...process.env, MIX_QUIET: "1" },
|
|
36
|
+
});
|
|
37
|
+
const manifestPath = resolve(dir, "otp/manifest.json");
|
|
38
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
39
|
+
assert(manifest.runtimeVariant === "core" ||
|
|
40
|
+
manifest.runtimeVariant === "crypto", "popcorn.cook did not report a runtime variant");
|
|
41
|
+
return {
|
|
42
|
+
dir,
|
|
43
|
+
runtimeVariant: manifest.runtimeVariant,
|
|
44
|
+
notes: manifest.notes ?? [],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
await rm(dir, { recursive: true, force: true });
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function runtimeDirectory(variant) {
|
|
53
|
+
const distDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
54
|
+
return resolve(distDir, "runtimes", variant);
|
|
55
|
+
}
|
|
56
|
+
function assert(ok, message) {
|
|
57
|
+
if (!ok)
|
|
58
|
+
throw new Error(`[popcorn-otp] ${message}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { popcorn as p, runtimeDirectory as r };
|
package/dist/plugins/vite.d.ts
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
-
import { type PopcornPluginOptions } from "./shared";
|
|
2
1
|
import type { Plugin } from "vite";
|
|
3
|
-
|
|
2
|
+
import { type Options } from "./shared";
|
|
3
|
+
/** Headers required by the threaded WebAssembly runtime. */
|
|
4
|
+
export declare const crossOriginIsolationHeaders: {
|
|
5
|
+
"Cross-Origin-Opener-Policy": string;
|
|
6
|
+
"Cross-Origin-Embedder-Policy": string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Serves application assets during developmens.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { defineConfig } from "vite";
|
|
14
|
+
* import { popcorn } from "@swmansion/popcorn/vite";
|
|
15
|
+
*
|
|
16
|
+
* export default defineConfig({
|
|
17
|
+
* plugins: [popcorn({ rootDir: "../my_app" })],
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @see {@link Options} for production server requirements.
|
|
22
|
+
*/
|
|
23
|
+
export declare function popcorn(options: Options): Plugin;
|
package/dist/plugins/vite.mjs
CHANGED
|
@@ -1,101 +1,224 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname,
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
1
|
+
import { mkdir, cp, rm, readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve, dirname, join, relative, isAbsolute } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { r as runtimeDirectory, p as popcorn$1 } from './shared.mjs';
|
|
5
|
+
import 'node:child_process';
|
|
6
|
+
import 'node:os';
|
|
7
|
+
import 'node:util';
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
/** Headers required by the threaded WebAssembly runtime. */
|
|
10
|
+
const crossOriginIsolationHeaders = {
|
|
11
|
+
"Cross-Origin-Opener-Policy": "same-origin",
|
|
12
|
+
"Cross-Origin-Embedder-Policy": "require-corp",
|
|
13
|
+
};
|
|
14
|
+
// Plugin is at dist/plugins/vite.mjs, dist/ is one level up.
|
|
15
|
+
const distDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
16
|
+
/**
|
|
17
|
+
* Serves application assets during developmens.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { defineConfig } from "vite";
|
|
22
|
+
* import { popcorn } from "@swmansion/popcorn/vite";
|
|
23
|
+
*
|
|
24
|
+
* export default defineConfig({
|
|
25
|
+
* plugins: [popcorn({ rootDir: "../my_app" })],
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @see {@link Options} for production server requirements.
|
|
30
|
+
*/
|
|
8
31
|
function popcorn(options) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
32
|
+
const resolveRuntime = async (source, importer) => {
|
|
33
|
+
if (importer !== undefined &&
|
|
34
|
+
resolve(dirname(importer.split("?")[0]), source) ===
|
|
35
|
+
join(distDir, "beam.mjs")) {
|
|
36
|
+
const { runtimeVariant } = await ensurePrepared();
|
|
37
|
+
const runtimeDir = runtimeDirectory(runtimeVariant);
|
|
38
|
+
return join(runtimeDir, "beam.mjs");
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
let prepared;
|
|
42
|
+
let repacking;
|
|
43
|
+
let dirty = false;
|
|
44
|
+
let outDir;
|
|
45
|
+
let assetsDir = "assets";
|
|
46
|
+
// Dev/preview: pack once at server startup, then re-pack lazily after the
|
|
47
|
+
// app's compiled beams change. A single in-flight promise dedupes concurrent
|
|
48
|
+
// requests; a change during a re-pack re-sets `dirty` so it isn't lost.
|
|
49
|
+
const ensurePrepared = () => {
|
|
50
|
+
if (prepared !== undefined && !dirty)
|
|
51
|
+
return Promise.resolve(prepared);
|
|
52
|
+
if (repacking === undefined) {
|
|
53
|
+
dirty = false;
|
|
54
|
+
repacking = popcorn$1(options)
|
|
55
|
+
.then(async (next) => {
|
|
56
|
+
if (prepared !== undefined) {
|
|
57
|
+
await rm(prepared.dir, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
prepared = next;
|
|
60
|
+
return next;
|
|
61
|
+
})
|
|
62
|
+
.finally(() => {
|
|
63
|
+
repacking = undefined;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return repacking;
|
|
67
|
+
};
|
|
68
|
+
const cleanup = () => {
|
|
69
|
+
if (prepared !== undefined) {
|
|
70
|
+
rm(prepared.dir, { recursive: true, force: true });
|
|
71
|
+
prepared = undefined;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const serve = async (req, res, next) => {
|
|
75
|
+
const url = req.url;
|
|
76
|
+
if (url === undefined) {
|
|
77
|
+
next();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const requestPath = decodeURIComponent(new URL(url, "http://localhost").pathname);
|
|
81
|
+
const requestRoot = [`/${assetsDir}/otp/`, `/@fs${distDir}/otp/`].find((root) => requestPath.startsWith(root));
|
|
82
|
+
if (requestRoot === undefined) {
|
|
83
|
+
next();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let dir;
|
|
87
|
+
try {
|
|
88
|
+
({ dir } = await ensurePrepared());
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
next(error);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const assetsRoot = join(dir, "otp");
|
|
95
|
+
const filePath = resolve(assetsRoot, requestPath.slice(requestRoot.length));
|
|
96
|
+
if (!isUnder(assetsRoot, filePath)) {
|
|
97
|
+
res.statusCode = 403;
|
|
98
|
+
res.end("Forbidden");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const compressible = isCompressible(filePath);
|
|
103
|
+
const encoding = compressible
|
|
104
|
+
? selectEncoding(req.headers["accept-encoding"]?.toString())
|
|
105
|
+
: { name: null, suffix: "" };
|
|
106
|
+
if (compressible && encoding.name === null) {
|
|
107
|
+
res.statusCode = 406;
|
|
108
|
+
res.setHeader("Vary", "Accept-Encoding");
|
|
109
|
+
res.end("No supported content encoding");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const content = await readFile(`${filePath}${encoding.suffix}`);
|
|
113
|
+
setContentType(res, filePath);
|
|
114
|
+
res.setHeader("Vary", "Accept-Encoding");
|
|
115
|
+
if (encoding.name !== null) {
|
|
116
|
+
res.setHeader("Content-Encoding", encoding.name);
|
|
117
|
+
}
|
|
118
|
+
res.end(content);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
res.statusCode = 404;
|
|
122
|
+
res.end("Not found");
|
|
123
|
+
}
|
|
124
|
+
};
|
|
16
125
|
return {
|
|
17
|
-
name: "popcorn",
|
|
126
|
+
name: "popcorn-otp",
|
|
127
|
+
enforce: "pre",
|
|
128
|
+
resolveId: resolveRuntime,
|
|
18
129
|
config() {
|
|
19
130
|
return {
|
|
20
|
-
// Exclude
|
|
21
|
-
optimizeDeps: {
|
|
22
|
-
|
|
131
|
+
// Exclude from prebundling so import.meta.url resolves correctly.
|
|
132
|
+
optimizeDeps: { exclude: ["@swmansion/popcorn"] },
|
|
133
|
+
worker: {
|
|
134
|
+
format: "es",
|
|
135
|
+
plugins: () => [
|
|
136
|
+
{
|
|
137
|
+
name: "popcorn-runtime",
|
|
138
|
+
enforce: "pre",
|
|
139
|
+
resolveId: resolveRuntime,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
23
142
|
},
|
|
24
143
|
};
|
|
25
144
|
},
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (result.status === "rejected") {
|
|
30
|
-
this.error(`[popcorn] Bundle doesn't exist at '${bundles[i].path}'`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
config.server.fs.allow.push(popcornDistDir);
|
|
145
|
+
configResolved(config) {
|
|
146
|
+
config.server.fs.allow.push(distDir);
|
|
147
|
+
outDir = resolve(config.root, config.build.outDir);
|
|
34
148
|
assetsDir = config.build.assetsDir;
|
|
35
149
|
},
|
|
36
|
-
configureServer(server) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
next();
|
|
150
|
+
async configureServer(server) {
|
|
151
|
+
const rootDir = resolve(options.rootDir);
|
|
152
|
+
server.httpServer?.prependListener("request", setIsolationHeaders);
|
|
153
|
+
server.watcher.add(rootDir);
|
|
154
|
+
server.watcher.on("all", (_event, file) => {
|
|
155
|
+
if (isUnder(rootDir, file))
|
|
156
|
+
dirty = true;
|
|
46
157
|
});
|
|
158
|
+
server.middlewares.use(serve);
|
|
159
|
+
server.httpServer?.once("close", cleanup);
|
|
160
|
+
await ensurePrepared();
|
|
161
|
+
dirty = false;
|
|
47
162
|
},
|
|
48
|
-
configurePreviewServer(server) {
|
|
49
|
-
server.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const served = await serveAvmBundle(req, res, opts);
|
|
54
|
-
if (served)
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
next();
|
|
58
|
-
});
|
|
163
|
+
async configurePreviewServer(server) {
|
|
164
|
+
server.httpServer?.prependListener("request", setIsolationHeaders);
|
|
165
|
+
server.middlewares.use(serve);
|
|
166
|
+
server.httpServer?.once("close", cleanup);
|
|
167
|
+
await ensurePrepared();
|
|
59
168
|
},
|
|
60
|
-
async
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
});
|
|
169
|
+
async closeBundle() {
|
|
170
|
+
assert(outDir !== undefined, "outDir was not resolved");
|
|
171
|
+
const built = await ensurePrepared();
|
|
172
|
+
try {
|
|
173
|
+
const targetDir = join(outDir, assetsDir, "otp");
|
|
174
|
+
await mkdir(dirname(targetDir), { recursive: true });
|
|
175
|
+
await cp(join(built.dir, "otp"), targetDir, { recursive: true });
|
|
68
176
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.emitFile({
|
|
73
|
-
type: "asset",
|
|
74
|
-
fileName: `${assetsDir}/${name}`,
|
|
75
|
-
source: await readFile(resolve(popcornDistDir, name)),
|
|
76
|
-
});
|
|
177
|
+
finally {
|
|
178
|
+
await rm(built.dir, { recursive: true, force: true });
|
|
179
|
+
prepared = undefined;
|
|
77
180
|
}
|
|
78
181
|
},
|
|
79
182
|
};
|
|
80
183
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
return false;
|
|
184
|
+
function isCompressible(path) {
|
|
185
|
+
return path.endsWith(".tar") || path.endsWith(".wasm");
|
|
186
|
+
}
|
|
187
|
+
function selectEncoding(header) {
|
|
188
|
+
if (header?.includes("br")) {
|
|
189
|
+
return { name: "br", suffix: ".br" };
|
|
90
190
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
191
|
+
if (header?.includes("gzip"))
|
|
192
|
+
return { name: "gzip", suffix: ".gz" };
|
|
193
|
+
return { name: null, suffix: "" };
|
|
194
|
+
}
|
|
195
|
+
function isUnder(dir, file) {
|
|
196
|
+
const rel = relative(dir, file);
|
|
197
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
198
|
+
}
|
|
199
|
+
function setIsolationHeaders(_req, res) {
|
|
200
|
+
for (const [key, value] of Object.entries(crossOriginIsolationHeaders)) {
|
|
201
|
+
res.setHeader(key, value);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function setContentType(res, path) {
|
|
205
|
+
if (path.endsWith(".mjs")) {
|
|
206
|
+
res.setHeader("Content-Type", "text/javascript");
|
|
207
|
+
}
|
|
208
|
+
else if (path.endsWith(".wasm")) {
|
|
209
|
+
res.setHeader("Content-Type", "application/wasm");
|
|
210
|
+
}
|
|
211
|
+
else if (path.endsWith(".json")) {
|
|
212
|
+
res.setHeader("Content-Type", "application/json");
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
res.setHeader("Content-Type", "application/octet-stream");
|
|
94
216
|
}
|
|
95
217
|
}
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
218
|
+
function assert(ok, message) {
|
|
219
|
+
if (!ok) {
|
|
220
|
+
throw new Error(`[popcorn-otp] ${message}`);
|
|
221
|
+
}
|
|
99
222
|
}
|
|
100
223
|
|
|
101
|
-
export { popcorn };
|
|
224
|
+
export { crossOriginIsolationHeaders, popcorn };
|