hikkaku 0.1.14 → 0.2.0

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/client/index.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ import { zipSync } from "fflate";
2
+
1
3
  //#region src/client/fiber.ts
2
4
  const findDOMAppRoot = () => {
3
5
  const probably = [document.getElementById("app"), document.querySelector("[class^=index_app]")];
@@ -59,7 +61,8 @@ import.meta.hot?.on("hikkaku:project", async (project) => {
59
61
  isFirstLoad = false;
60
62
  }
61
63
  console.log("Received updated project:", project);
62
- await state.vm.loadProject(project).catch(console.error);
64
+ const projectSB3 = zipSync({ "project.json": new TextEncoder().encode(JSON.stringify(project)) });
65
+ await state.vm.loadProject(projectSB3).catch(console.error);
63
66
  console.log("Project loaded.");
64
67
  });
65
68
 
@@ -1,9 +1,37 @@
1
+ import { InputType, Shadow } from "sb3-types/enum";
2
+
3
+ //#region src/core/sb3-enum.ts
4
+ const toNumericEnum = (value, fallback) => {
5
+ if (typeof value === "number" && Number.isInteger(value)) return value;
6
+ if (typeof value === "string") {
7
+ const normalized = value.trim();
8
+ if (normalized.length === 0) return fallback;
9
+ const parsed = Number(normalized);
10
+ if (Number.isInteger(parsed)) return parsed;
11
+ }
12
+ return fallback;
13
+ };
14
+ const shadow = Shadow;
15
+ const inputType = InputType;
16
+ const Shadow$1 = {
17
+ SameBlockShadow: toNumericEnum(shadow.SameBlockShadow ?? shadow.UnObscured, 1),
18
+ NoShadow: toNumericEnum(shadow.NoShadow ?? shadow.No, 2),
19
+ DiffBlockShadow: toNumericEnum(shadow.DiffBlockShadow ?? shadow.Obscured, 3)
20
+ };
21
+ const InputType$1 = {
22
+ Number: toNumericEnum(inputType.Number, 4),
23
+ PositiveInteger: toNumericEnum(inputType.PositiveInteger ?? inputType.PossiveInteger, 6),
24
+ String: toNumericEnum(inputType.String, 10),
25
+ Broadcast: toNumericEnum(inputType.Broadcast, 11)
26
+ };
27
+
28
+ //#endregion
1
29
  //#region src/core/block-helper.ts
2
30
  const fromPrimitiveSource = (source) => {
3
- if (typeof source === "number") return [1, [4, source]];
4
- if (typeof source === "boolean") return [1, [6, source ? 1 : 0]];
5
- if (typeof source === "string") return [1, [10, source]];
6
- return [1, source.id];
31
+ if (typeof source === "number") return [Shadow$1.SameBlockShadow, [InputType$1.Number, source]];
32
+ if (typeof source === "boolean") return [Shadow$1.SameBlockShadow, [InputType$1.PositiveInteger, source ? 1 : 0]];
33
+ if (typeof source === "string") return [Shadow$1.SameBlockShadow, [InputType$1.String, source]];
34
+ return [Shadow$1.SameBlockShadow, source.id];
7
35
  };
8
36
  const fromCostumeSource = (source) => {
9
37
  if (typeof source === "object" && source !== null && "type" in source && source.type === "costume") return fromPrimitiveSource(source.name);
@@ -88,8 +116,35 @@ const catchNewBlocks = (handler, catched) => {
88
116
  ctx.adder = (id, block) => {
89
117
  catched(id, block);
90
118
  };
91
- handler();
92
- ctx.adder = oldAdder;
119
+ try {
120
+ handler();
121
+ } finally {
122
+ ctx.adder = oldAdder;
123
+ }
124
+ };
125
+ const firstExecutableBlockId = (blocks) => {
126
+ const ctx = getRootContext();
127
+ for (const block of blocks) {
128
+ if (!block || ctx.usedAsValueSet.has(block)) continue;
129
+ const blockId = ctx.blockToId.get(block);
130
+ if (blockId) return blockId;
131
+ }
132
+ return null;
133
+ };
134
+ const layoutTopLevelBlocks = (blocks) => {
135
+ const ctx = getRootContext();
136
+ const seen = /* @__PURE__ */ new Set();
137
+ let index = 0;
138
+ for (const block of blocks) {
139
+ if (!block || !block.topLevel) continue;
140
+ const blockId = ctx.blockToId.get(block);
141
+ if (!blockId || seen.has(blockId)) continue;
142
+ seen.add(blockId);
143
+ if (block.x !== 0 || block.y !== 0) continue;
144
+ block.x = 64;
145
+ block.y = 72 + index * 260;
146
+ index++;
147
+ }
93
148
  };
94
149
  const substack = (handler) => {
95
150
  const ctx = getRootContext();
@@ -99,12 +154,27 @@ const substack = (handler) => {
99
154
  blocks.push(block);
100
155
  });
101
156
  applyNextAndParent(blocks);
102
- for (const block of blocks) {
103
- if (!block || ctx.usedAsValueSet.has(block)) continue;
104
- const blockId = ctx.blockToId.get(block);
105
- if (blockId) return blockId;
157
+ return firstExecutableBlockId(blocks);
158
+ };
159
+ const attachStack = (parentId, handler) => {
160
+ if (!handler) return null;
161
+ const ctx = getRootContext();
162
+ const blocks = [];
163
+ catchNewBlocks(handler, (id, block) => {
164
+ ctx.blocks[id] = block;
165
+ blocks.push(block);
166
+ });
167
+ const stackBlocks = blocks.filter((block) => !block.topLevel);
168
+ applyNextAndParent(stackBlocks);
169
+ const firstBlockId = firstExecutableBlockId(stackBlocks);
170
+ if (!firstBlockId) return null;
171
+ const parentBlock = ctx.blocks[parentId];
172
+ const firstBlock = ctx.blocks[firstBlockId];
173
+ if (parentBlock && firstBlock) {
174
+ parentBlock.next = firstBlockId;
175
+ firstBlock.parent = parentId;
106
176
  }
107
- return null;
177
+ return firstBlockId;
108
178
  };
109
179
  const createBlocks = (handler) => {
110
180
  const blocks = {};
@@ -120,6 +190,7 @@ const createBlocks = (handler) => {
120
190
  blocksForAddingNext.push(block);
121
191
  });
122
192
  applyNextAndParent(blocksForAddingNext);
193
+ layoutTopLevelBlocks(blocksForAddingNext);
123
194
  const unconnectedValueBlocks = [];
124
195
  for (const [blockId, block] of Object.entries(blocks)) if (rootContext.valueBlockSet.has(block) && !rootContext.usedAsValueSet.has(block)) unconnectedValueBlocks.push({
125
196
  id: blockId,
@@ -135,4 +206,4 @@ const createBlocks = (handler) => {
135
206
  };
136
207
 
137
208
  //#endregion
138
- export { fromCostumeSource as a, valueBlock as i, createBlocks as n, fromPrimitiveSource as o, substack as r, fromSoundSource as s, block as t };
209
+ export { valueBlock as a, fromSoundSource as c, substack as i, InputType$1 as l, block as n, fromCostumeSource as o, createBlocks as r, fromPrimitiveSource as s, attachStack as t, Shadow$1 as u };
package/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as PrimitiveSource, a as block, b as VariableBase, c as valueBlock, d as fromSoundSource, f as CostumeReference, g as PrimitiveAvailableOnScratch, h as ListReference, i as Handler, l as fromCostumeSource, m as HikkakuBlock, n as Target, o as createBlocks, p as CostumeSource, r as BlockInit, s as substack, t as Project, u as fromPrimitiveSource, v as SoundReference, x as VariableReference, y as SoundSource } from "./project-BSiPw18H.mjs";
2
- export { BlockInit, CostumeReference, CostumeSource, Handler, HikkakuBlock, ListReference, PrimitiveAvailableOnScratch, PrimitiveSource, Project, SoundReference, SoundSource, Target, VariableBase, VariableReference, block, createBlocks, fromCostumeSource, fromPrimitiveSource, fromSoundSource, substack, valueBlock };
1
+ import { C as SoundReference, D as VariableMonitorMode, E as VariableDefinition, O as VariableMonitorOptions, S as PrimitiveSource, T as VariableBase, _ as HikkakuBlock, a as attachStack, b as MonitorPosition, c as substack, d as fromPrimitiveSource, f as fromSoundSource, g as CreateVariableOptions, h as CreateListOptions, i as Handler, k as VariableReference, l as valueBlock, m as CostumeSource, n as Target, o as block, p as CostumeReference, r as BlockInit, s as createBlocks, t as Project, u as fromCostumeSource, v as ListMonitorOptions, w as SoundSource, x as PrimitiveAvailableOnScratch, y as ListReference } from "./project-Ca9rsVT3.mjs";
2
+ export { BlockInit, CostumeReference, CostumeSource, CreateListOptions, CreateVariableOptions, Handler, HikkakuBlock, ListMonitorOptions, ListReference, MonitorPosition, PrimitiveAvailableOnScratch, PrimitiveSource, Project, SoundReference, SoundSource, Target, VariableBase, VariableDefinition, VariableMonitorMode, VariableMonitorOptions, VariableReference, attachStack, block, createBlocks, fromCostumeSource, fromPrimitiveSource, fromSoundSource, substack, valueBlock };
package/index.mjs CHANGED
@@ -1,5 +1,54 @@
1
- import { a as fromCostumeSource, i as valueBlock, n as createBlocks, o as fromPrimitiveSource, r as substack, s as fromSoundSource, t as block } from "./composer-JWWbnKkh.mjs";
1
+ import { a as valueBlock, c as fromSoundSource, i as substack, n as block, o as fromCostumeSource, r as createBlocks, s as fromPrimitiveSource, t as attachStack } from "./composer-BudVTTBs.mjs";
2
2
 
3
+ //#region src/core/monitors.ts
4
+ const createVariableMonitor = (id, name, defaultValue, spriteName, options) => {
5
+ const sliderMin = options.sliderMin ?? 0;
6
+ const sliderMax = options.sliderMax ?? 100;
7
+ return {
8
+ id,
9
+ mode: options.mode ?? "default",
10
+ opcode: "data_variable",
11
+ params: { VARIABLE: name },
12
+ spriteName,
13
+ value: defaultValue,
14
+ sliderMin: Math.min(sliderMin, sliderMax),
15
+ sliderMax: Math.max(sliderMin, sliderMax),
16
+ isDiscrete: options.isDiscrete ?? true,
17
+ x: options.x ?? null,
18
+ y: options.y ?? null,
19
+ width: 0,
20
+ height: 0,
21
+ visible: options.visible ?? true
22
+ };
23
+ };
24
+ const createListMonitor = (id, name, defaultValue, spriteName, options) => {
25
+ return {
26
+ id,
27
+ mode: "list",
28
+ opcode: "data_listcontents",
29
+ params: { LIST: name },
30
+ spriteName,
31
+ value: [...defaultValue],
32
+ x: options.x ?? null,
33
+ y: options.y ?? null,
34
+ width: options.width ?? 0,
35
+ height: options.height ?? 0,
36
+ visible: options.visible ?? true
37
+ };
38
+ };
39
+ const cloneMonitor = (monitor) => {
40
+ if (monitor.mode === "list") return {
41
+ ...monitor,
42
+ params: { ...monitor.params },
43
+ value: [...monitor.value]
44
+ };
45
+ return {
46
+ ...monitor,
47
+ params: { ...monitor.params }
48
+ };
49
+ };
50
+
51
+ //#endregion
3
52
  //#region src/core/project.ts
4
53
  let nextAssetId = 0;
5
54
  const createAssetId = () => `asset-${(++nextAssetId).toString(16)}`;
@@ -34,6 +83,7 @@ var Target = class {
34
83
  #blocks = {};
35
84
  #variables = {};
36
85
  #lists = {};
86
+ #monitors = [];
37
87
  #costumes = [];
38
88
  #sounds = [];
39
89
  constructor(isStage, name) {
@@ -49,22 +99,30 @@ var Target = class {
49
99
  ...blocks
50
100
  };
51
101
  }
52
- createVariable(name, defaultValue = 0, isCloudVariable) {
102
+ createVariable(name, defaultValue = 0, isCloudVariableOrOptions) {
103
+ const options = typeof isCloudVariableOrOptions === "boolean" ? { isCloudVariable: isCloudVariableOrOptions } : isCloudVariableOrOptions;
53
104
  const id = createAssetId();
54
- this.#variables[id] = isCloudVariable ? [
105
+ this.#variables[id] = options?.isCloudVariable ? [
55
106
  name,
56
107
  defaultValue,
57
108
  true
58
109
  ] : [name, defaultValue];
110
+ if (options?.monitor) this.#monitors.push(createVariableMonitor(id, name, defaultValue, this.isStage ? null : this.name, options.monitor));
59
111
  return {
60
112
  id,
61
113
  name,
62
- type: "variable"
114
+ type: "variable",
115
+ get: () => valueBlock("data_variable", { fields: { VARIABLE: [name, id] } }),
116
+ set: (value) => block("data_setvariableto", {
117
+ inputs: { VALUE: fromPrimitiveSource(value) },
118
+ fields: { VARIABLE: [name, id] }
119
+ })
63
120
  };
64
121
  }
65
- createList(name, defaultValue = []) {
122
+ createList(name, defaultValue = [], options) {
66
123
  const id = createAssetId();
67
124
  this.#lists[id] = [name, defaultValue];
125
+ if (options?.monitor) this.#monitors.push(createListMonitor(id, name, defaultValue, this.isStage ? null : this.name, options.monitor));
68
126
  return {
69
127
  id,
70
128
  name,
@@ -85,6 +143,9 @@ var Target = class {
85
143
  type: "sound"
86
144
  };
87
145
  }
146
+ get monitors() {
147
+ return this.#monitors;
148
+ }
88
149
  toScratch() {
89
150
  const costumes = this.#costumes.length > 0 ? this.#costumes : [{
90
151
  name: this.name,
@@ -98,7 +159,8 @@ var Target = class {
98
159
  lists: this.#lists,
99
160
  sounds: this.#sounds,
100
161
  currentCostume: this.currentCostume,
101
- costumes
162
+ costumes,
163
+ comments: {}
102
164
  };
103
165
  if (this.isStage) return {
104
166
  ...target,
@@ -128,18 +190,17 @@ var Project = class {
128
190
  }
129
191
  toScratch() {
130
192
  const targets = this.#targets.map((target) => target.toScratch());
131
- const extensions = collectExtensions(targets);
132
- const project = {
193
+ return {
133
194
  targets,
195
+ monitors: this.#targets.flatMap((target) => target.monitors.map((monitor) => cloneMonitor(monitor))),
196
+ extensions: collectExtensions(targets),
134
197
  meta: {
135
198
  semver: "3.0.0",
136
- agent: `Hikkaku | ${globalThis.navigator ? navigator.userAgent : "unknown"}`
199
+ agent: `Hikkaku | ${typeof navigator !== "undefined" ? navigator.userAgent : "unknown"}`
137
200
  }
138
201
  };
139
- if (extensions.length > 0) project.extensions = extensions;
140
- return project;
141
202
  }
142
203
  };
143
204
 
144
205
  //#endregion
145
- export { Project, Target, block, createBlocks, fromCostumeSource, fromPrimitiveSource, fromSoundSource, substack, valueBlock };
206
+ export { Project, Target, attachStack, block, createBlocks, fromCostumeSource, fromPrimitiveSource, fromSoundSource, substack, valueBlock };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hikkaku",
3
- "version": "0.1.14",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "exports": {
@@ -30,15 +30,18 @@
30
30
  "typecheck": "tsgo"
31
31
  },
32
32
  "devDependencies": {
33
+ "@turbowarp/packager": "^3.11.0",
33
34
  "@types/bun": "latest",
34
- "tsdown": "^0.20.0-beta.4",
35
- "@typescript/native-preview": "^7.0.0-dev.20260127.1"
35
+ "@typescript/native-preview": "^7.0.0-dev.20260127.1",
36
+ "rolldown": "1.0.0-beta.60",
37
+ "tsdown": "^0.20.0-beta.4"
36
38
  },
37
39
  "peerDependencies": {
38
40
  "typescript": "^5"
39
41
  },
40
42
  "dependencies": {
41
- "sb3-types": "0.1.10",
43
+ "fflate": "^0.8.2",
44
+ "sb3-types": "^0.1.16",
42
45
  "vite": "8.0.0-beta.8"
43
46
  },
44
47
  "repository": {
@@ -0,0 +1,139 @@
1
+ import * as sb3 from "sb3-types";
2
+
3
+ //#region src/core/types.d.ts
4
+ type PrimitiveAvailableOnScratch = number | boolean | string;
5
+ type PrimitiveSource<T extends PrimitiveAvailableOnScratch> = T | HikkakuBlock;
6
+ interface VariableBase {
7
+ id: string;
8
+ name: string;
9
+ }
10
+ interface MonitorPosition {
11
+ x?: number | null;
12
+ y?: number | null;
13
+ }
14
+ type VariableMonitorMode = 'default' | 'large' | 'slider';
15
+ interface VariableMonitorOptions extends MonitorPosition {
16
+ visible?: boolean;
17
+ mode?: VariableMonitorMode;
18
+ sliderMin?: number;
19
+ sliderMax?: number;
20
+ isDiscrete?: boolean;
21
+ }
22
+ interface ListMonitorOptions extends MonitorPosition {
23
+ visible?: boolean;
24
+ width?: number;
25
+ height?: number;
26
+ }
27
+ interface CreateVariableOptions {
28
+ isCloudVariable?: boolean;
29
+ monitor?: VariableMonitorOptions;
30
+ }
31
+ interface CreateListOptions {
32
+ monitor?: ListMonitorOptions;
33
+ }
34
+ interface VariableReference extends VariableBase {
35
+ type: 'variable';
36
+ }
37
+ interface VariableDefinition extends VariableReference {
38
+ get(): HikkakuBlock;
39
+ set(value: PrimitiveSource<number | string>): HikkakuBlock;
40
+ }
41
+ interface ListReference extends VariableBase {
42
+ type: 'list';
43
+ }
44
+ interface CostumeReference {
45
+ name: string;
46
+ type: 'costume';
47
+ }
48
+ type CostumeSource = PrimitiveSource<string> | CostumeReference;
49
+ interface SoundReference {
50
+ name: string;
51
+ type: 'sound';
52
+ }
53
+ type SoundSource = PrimitiveSource<string> | SoundReference;
54
+ interface HikkakuBlock {
55
+ isBlock: true;
56
+ id: string;
57
+ }
58
+ //#endregion
59
+ //#region src/core/block-helper.d.ts
60
+ declare const fromPrimitiveSource: <T extends PrimitiveAvailableOnScratch>(source: PrimitiveSource<T>) => sb3.Input;
61
+ declare const fromCostumeSource: (source: CostumeSource) => sb3.Input;
62
+ declare const fromSoundSource: (source: SoundSource) => sb3.Input;
63
+ //#endregion
64
+ //#region src/core/composer.d.ts
65
+ type Handler = () => void;
66
+ interface BlockInit {
67
+ inputs?: Record<string, sb3.Input>;
68
+ fields?: Record<string, sb3.Fields>;
69
+ topLevel?: boolean;
70
+ mutation?: sb3.Mutation;
71
+ isShadow?: boolean;
72
+ isValue?: boolean;
73
+ }
74
+ declare const block: (opcode: string, init: BlockInit) => HikkakuBlock;
75
+ declare const valueBlock: (opcode: string, init: BlockInit) => HikkakuBlock;
76
+ declare const substack: (handler: Handler) => string | null;
77
+ declare const attachStack: (parentId: string, handler?: Handler) => string | null;
78
+ declare const createBlocks: (handler: Handler) => Record<string, sb3.Block>;
79
+ //#endregion
80
+ //#region src/core/monitors.d.ts
81
+ interface VariableMonitor {
82
+ id: string;
83
+ mode: 'default' | 'large' | 'slider';
84
+ opcode: 'data_variable';
85
+ params: {
86
+ VARIABLE: string;
87
+ };
88
+ spriteName: string | null;
89
+ value: sb3.ScalarVal;
90
+ sliderMin: number;
91
+ sliderMax: number;
92
+ isDiscrete: boolean;
93
+ x: number | null;
94
+ y: number | null;
95
+ width: number;
96
+ height: number;
97
+ visible: boolean;
98
+ }
99
+ interface ListMonitor {
100
+ id: string;
101
+ mode: 'list';
102
+ opcode: 'data_listcontents';
103
+ params: {
104
+ LIST: string;
105
+ };
106
+ spriteName: string | null;
107
+ value: sb3.ScalarVal[];
108
+ x: number | null;
109
+ y: number | null;
110
+ width: number;
111
+ height: number;
112
+ visible: boolean;
113
+ }
114
+ type Monitor = VariableMonitor | ListMonitor;
115
+ //#endregion
116
+ //#region src/core/project.d.ts
117
+ declare class Target<IsStage extends boolean = boolean> {
118
+ #private;
119
+ readonly isStage: IsStage;
120
+ readonly name: IsStage extends true ? 'Stage' : string;
121
+ currentCostume: number;
122
+ constructor(isStage: IsStage, name: IsStage extends true ? 'Stage' : string);
123
+ run(handler: (target: Target<IsStage>) => void): void;
124
+ createVariable(name: string, defaultValue?: sb3.ScalarVal, isCloudVariableOrOptions?: boolean | CreateVariableOptions): VariableDefinition;
125
+ createList(name: string, defaultValue?: sb3.ScalarVal[], options?: CreateListOptions): ListReference;
126
+ addCostume(costume: sb3.Costume): CostumeReference;
127
+ addSound(sound: sb3.Sound): SoundReference;
128
+ get monitors(): readonly Monitor[];
129
+ toScratch(): IsStage extends true ? sb3.Stage : sb3.Sprite;
130
+ }
131
+ declare class Project {
132
+ #private;
133
+ readonly stage: Target<true>;
134
+ constructor();
135
+ createSprite(name: string): Target<false>;
136
+ toScratch(): sb3.ScratchProject;
137
+ }
138
+ //#endregion
139
+ export { SoundReference as C, VariableMonitorMode as D, VariableDefinition as E, VariableMonitorOptions as O, PrimitiveSource as S, VariableBase as T, HikkakuBlock as _, attachStack as a, MonitorPosition as b, substack as c, fromPrimitiveSource as d, fromSoundSource as f, CreateVariableOptions as g, CreateListOptions as h, Handler as i, VariableReference as k, valueBlock as l, CostumeSource as m, Target as n, block as o, CostumeReference as p, BlockInit as r, createBlocks as s, Project as t, fromCostumeSource as u, ListMonitorOptions as v, SoundSource as w, PrimitiveAvailableOnScratch as x, ListReference as y };
package/vite/index.mjs CHANGED
@@ -1,29 +1,93 @@
1
+ import { zip } from "fflate";
2
+ import { mkdir, rm, writeFile } from "node:fs/promises";
3
+ import * as path from "node:path";
4
+ import { pathToFileURL } from "node:url";
1
5
  import { createServerModuleRunner } from "vite";
2
6
 
3
7
  //#region src/vite/index.ts
4
8
  const BASE_URL = "https://scratchfoundation.github.io/scratch-gui/";
9
+ const VIRTUAL_MODULE_IDS = { project: "/@virtual/hikkaku-project" };
5
10
  function hikkaku(init) {
6
11
  let runner = null;
7
12
  return {
8
13
  name: "vite-plugin-hikkaku",
9
- config() {
10
- return { environments: {
11
- hikkaku: {},
12
- client: {}
13
- } };
14
+ config(config, env) {
15
+ if (env.command === "build") (config.plugins?.find((p) => p && typeof p === "object" && "name" in p && p.name === "vite-plugin-turbowarp-packager"))?.api.setEntry(path.join(process.cwd(), "dist", "project.sb3"));
16
+ return {
17
+ environments: { hikkaku: { build: { rolldownOptions: {
18
+ input: init.entry,
19
+ output: {
20
+ entryFileNames: "project.mjs",
21
+ format: "es"
22
+ }
23
+ } } } },
24
+ builder: {}
25
+ };
26
+ },
27
+ async buildApp(builder) {
28
+ const env = builder.environments.hikkaku;
29
+ if (!env) throw new Error("Hikkaku environment is not configured.");
30
+ await builder.build(env);
31
+ },
32
+ async generateBundle(_options, bundle) {
33
+ const tmpDir = path.join(process.cwd(), "dist", ".tmp");
34
+ for (const [filePath, file] of Object.entries(bundle)) if (file.type === "chunk") {
35
+ const fullPath = path.join(tmpDir, filePath);
36
+ await mkdir(path.dirname(fullPath), { recursive: true });
37
+ await writeFile(fullPath, file.code);
38
+ }
39
+ const { default: project } = await import(pathToFileURL(path.join(process.cwd(), "dist/.tmp", "project.mjs")).href);
40
+ const projectJSON = project.toScratch();
41
+ const zipData = await new Promise((resolve, reject) => {
42
+ zip({ "project.json": new TextEncoder().encode(JSON.stringify(projectJSON)) }, (err, data) => {
43
+ if (err) reject(err);
44
+ else resolve(data);
45
+ });
46
+ });
47
+ this.emitFile({
48
+ type: "asset",
49
+ fileName: "project.sb3",
50
+ name: "project.sb3",
51
+ source: zipData
52
+ });
53
+ this.emitFile({
54
+ type: "asset",
55
+ fileName: "project.json",
56
+ name: "project.json",
57
+ source: JSON.stringify(projectJSON, null, 2)
58
+ });
59
+ await rm(tmpDir, {
60
+ recursive: true,
61
+ force: true
62
+ });
14
63
  },
15
64
  resolveId(source) {
16
65
  if (source === "/@virtual/hikkaku-client") return source;
66
+ if (source === VIRTUAL_MODULE_IDS.project) return source;
17
67
  },
18
- load(id) {
68
+ async load(id) {
19
69
  if (id === "/@virtual/hikkaku-client") return `
20
70
  import 'hikkaku/client'
21
71
  `;
72
+ if (id === VIRTUAL_MODULE_IDS.project) {
73
+ if (this.environment.mode === "dev") {
74
+ if (!runner) throw new Error("Module runner is not initialized.");
75
+ const project = (await runner.import(init.entry)).default;
76
+ return `
77
+ export default ${JSON.stringify(project.toScratch())}
78
+ `;
79
+ } else if (this.environment.mode === "build") {
80
+ const projectJSON = await import(pathToFileURL(path.join(process.cwd(), "dist", "project.json")).href, { with: { type: "json" } });
81
+ return `
82
+ export default ${JSON.stringify(projectJSON)}
83
+ `;
84
+ }
85
+ }
22
86
  },
23
87
  async hotUpdate(options) {
24
88
  if (this.environment.name !== "hikkaku") return;
25
89
  if (!runner) throw new Error("Module runner is not initialized.");
26
- const project = (await runner.import(options.file)).default;
90
+ const project = (await runner.import(init.entry)).default;
27
91
  options.server.environments.client.hot.send("hikkaku:project", project.toScratch());
28
92
  },
29
93
  async configureServer(server) {
@@ -1,73 +0,0 @@
1
- import * as sb3 from "sb3-types";
2
-
3
- //#region src/core/types.d.ts
4
- type PrimitiveAvailableOnScratch = number | boolean | string;
5
- type PrimitiveSource<T extends PrimitiveAvailableOnScratch> = T | HikkakuBlock;
6
- interface VariableBase {
7
- id: string;
8
- name: string;
9
- }
10
- interface VariableReference extends VariableBase {
11
- type: 'variable';
12
- }
13
- interface ListReference extends VariableBase {
14
- type: 'list';
15
- }
16
- interface CostumeReference {
17
- name: string;
18
- type: 'costume';
19
- }
20
- type CostumeSource = PrimitiveSource<string> | CostumeReference;
21
- interface SoundReference {
22
- name: string;
23
- type: 'sound';
24
- }
25
- type SoundSource = PrimitiveSource<string> | SoundReference;
26
- interface HikkakuBlock {
27
- isBlock: true;
28
- id: string;
29
- }
30
- //#endregion
31
- //#region src/core/block-helper.d.ts
32
- declare const fromPrimitiveSource: <T extends PrimitiveAvailableOnScratch>(source: PrimitiveSource<T>) => sb3.Input;
33
- declare const fromCostumeSource: (source: CostumeSource) => sb3.Input;
34
- declare const fromSoundSource: (source: SoundSource) => sb3.Input;
35
- //#endregion
36
- //#region src/core/composer.d.ts
37
- type Handler = () => void;
38
- interface BlockInit {
39
- inputs?: Record<string, sb3.Input>;
40
- fields?: Record<string, sb3.Fields>;
41
- topLevel?: boolean;
42
- mutation?: sb3.Mutation;
43
- isShadow?: boolean;
44
- isValue?: boolean;
45
- }
46
- declare const block: (opcode: string, init: BlockInit) => HikkakuBlock;
47
- declare const valueBlock: (opcode: string, init: BlockInit) => HikkakuBlock;
48
- declare const substack: (handler: Handler) => string | null;
49
- declare const createBlocks: (handler: Handler) => Record<string, sb3.Block>;
50
- //#endregion
51
- //#region src/core/project.d.ts
52
- declare class Target<IsStage extends boolean = boolean> {
53
- #private;
54
- readonly isStage: IsStage;
55
- readonly name: IsStage extends true ? 'Stage' : string;
56
- currentCostume: number;
57
- constructor(isStage: IsStage, name: IsStage extends true ? 'Stage' : string);
58
- run(handler: (target: Target<IsStage>) => void): void;
59
- createVariable(name: string, defaultValue?: sb3.ScalarVal, isCloudVariable?: boolean): VariableReference;
60
- createList(name: string, defaultValue?: sb3.ScalarVal[]): ListReference;
61
- addCostume(costume: sb3.Costume): CostumeReference;
62
- addSound(sound: sb3.Sound): SoundReference;
63
- toScratch(): IsStage extends true ? sb3.Stage : sb3.Sprite;
64
- }
65
- declare class Project {
66
- #private;
67
- readonly stage: Target<true>;
68
- constructor();
69
- createSprite(name: string): Target<false>;
70
- toScratch(): sb3.ScratchProject;
71
- }
72
- //#endregion
73
- export { PrimitiveSource as _, block as a, VariableBase as b, valueBlock as c, fromSoundSource as d, CostumeReference as f, PrimitiveAvailableOnScratch as g, ListReference as h, Handler as i, fromCostumeSource as l, HikkakuBlock as m, Target as n, createBlocks as o, CostumeSource as p, BlockInit as r, substack as s, Project as t, fromPrimitiveSource as u, SoundReference as v, VariableReference as x, SoundSource as y };