tw-plugin-webpack 1.0.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.
@@ -0,0 +1,331 @@
1
+ "use strict";
2
+ const __rslib_import_meta_url__ = /*#__PURE__*/ (function () {
3
+ return typeof document === 'undefined'
4
+ ? new (require('url'.replace('', '')).URL)('file:' + __filename).href
5
+ : (document.currentScript && document.currentScript.src) ||
6
+ new URL('main.js', document.baseURI).href;
7
+ })();
8
+ ;
9
+ // The require scope
10
+ var __webpack_require__ = {};
11
+
12
+ // webpack/runtime/define_property_getters
13
+ (() => {
14
+ __webpack_require__.d = (exports, getters, values) => {
15
+ var define = (defs, kind) => {
16
+ for(var key in defs) {
17
+ if(__webpack_require__.o(defs, key) && !__webpack_require__.o(exports, key)) {
18
+ Object.defineProperty(exports, key, { enumerable: true, [kind]: defs[key] });
19
+ }
20
+ }
21
+ };
22
+ define(getters, "get");
23
+ define(values, "value");
24
+ };
25
+ })();
26
+ // webpack/runtime/has_own_property
27
+ (() => {
28
+ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
29
+ })();
30
+ // webpack/runtime/make_namespace_object
31
+ (() => {
32
+ // define __esModule on exports
33
+ __webpack_require__.r = (exports) => {
34
+ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
35
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
36
+ }
37
+ Object.defineProperty(exports, '__esModule', { value: true });
38
+ };
39
+ })();
40
+ var __webpack_exports__ = {};
41
+ __webpack_require__.r(__webpack_exports__);
42
+ /**
43
+ * tw-plugin-webpack — bundle a multi-file TurboWarp / Scratch extension into a
44
+ * single unsandboxed-extension file with **webpack** or **Rspack**.
45
+ *
46
+ * Write your extension across as many ES modules as you like, `export default`
47
+ * the extension class (or an already-constructed instance) from the entry
48
+ * module, and this plugin wraps the bundle in the standard TurboWarp IIFE
49
+ * template and calls `Scratch.extensions.register()` for you:
50
+ *
51
+ * ```js
52
+ * (function (Scratch) {
53
+ * "use strict";
54
+ * // ...all of your bundled modules, inlined...
55
+ * Scratch.extensions.register(new MyExtension());
56
+ * })(Scratch);
57
+ * ```
58
+ *
59
+ * Because the entire bundle lives inside the IIFE, every bare reference to the
60
+ * `Scratch` global inside your code resolves to the local parameter — exactly
61
+ * the "personal copy of the Scratch API" the TurboWarp docs recommend.
62
+ *
63
+ * @module tw-plugin-webpack
64
+ */ const PLUGIN_NAME = 'TurboWarpExtensionPlugin';
65
+ // Assets imported by the extension are inlined as base64 `data:` URIs by
66
+ // default — a TurboWarp extension is a single file and can't reference separate
67
+ // asset files. SVG/PNG/etc. imports become the `data:` strings you hand to
68
+ // `menuIconURI` / `blockIconURI`.
69
+ const DEFAULT_ASSET_PATTERN = /\.(svg|png|jpe?g|gif|webp|avif)$/i;
70
+ /**
71
+ * Registry metadata. Each field becomes a `// Key: Value` comment line at the
72
+ * very top of the file — the header the
73
+ * [TurboWarp extensions gallery](https://github.com/TurboWarp/extensions)
74
+ * requires for submission:
75
+ *
76
+ * ```js
77
+ * // Name: Consoles
78
+ * // ID: sipcconsole
79
+ * // Description: Blocks that interact with the developer console.
80
+ * // By: -SIPC-
81
+ * // License: MIT
82
+ * ```
83
+ *
84
+ * @typedef {object} TurboWarpExtensionMetadata
85
+ * @property {string} [name] Display name shown in the extension list.
86
+ * @property {string} [id] Unique extension id. **Must match** the `id` your
87
+ * `getInfo()` returns.
88
+ * @property {string} [description] One-line description for the gallery.
89
+ * @property {string | string[]} [by] Author(s). Each entry becomes its own
90
+ * `// By:` line and may include a profile link, e.g.
91
+ * `"GarboMuffin <https://scratch.mit.edu/users/GarboMuffin/>"`.
92
+ * @property {string | string[]} [original] Original author(s) when this is a
93
+ * derivative — one `// Original:` line each.
94
+ * @property {string} [license] SPDX license id, e.g. `"MPL-2.0"`.
95
+ * @property {string} [context] Extra `// Context:` line.
96
+ */ /**
97
+ * @typedef {object} TurboWarpExtensionPluginOptions
98
+ * @property {boolean} [register=true] Append a
99
+ * `Scratch.extensions.register(...)` call for the entry's chosen export. Set
100
+ * to `false` if you would rather call `register()` yourself somewhere in your
101
+ * own code (it still runs inside the IIFE, so the `Scratch` global is
102
+ * available there too).
103
+ * @property {boolean} [unsandboxed=false] Emit a guard at the top of the bundle
104
+ * that throws unless the extension is running unsandboxed
105
+ * (`Scratch.extensions.unsandboxed`). Use this for extensions that require
106
+ * direct access to the VM.
107
+ * @property {TurboWarpExtensionMetadata} [metadata] Registry metadata injected
108
+ * as the `// Name:` / `// ID:` / … comment header the TurboWarp gallery reads.
109
+ * Omit it for extensions you only ever load manually.
110
+ * @property {boolean | RegExp} [inlineAssets=true] Configure the bundler so
111
+ * importing an asset inlines it as a base64 `data:` URI — the form TurboWarp
112
+ * wants for `menuIconURI` / `blockIconURI`. With this on you can
113
+ * `import iconURI from './icon.svg'` and use `iconURI` directly. Defaults to
114
+ * matching `svg`, `png`, `jpg`, `gif`, `webp`, `avif`; pass a `RegExp` to use
115
+ * your own test, or `false` to leave asset handling to your own config.
116
+ * @property {string} [name] Human-readable name used in the unsandboxed-guard
117
+ * error message. Defaults to `metadata.name` when set, otherwise
118
+ * `"This extension"`.
119
+ * @property {string} [varName="__turbowarpExtension__"] Identifier the bundle's
120
+ * export is assigned to before registration. Only change it if it somehow
121
+ * collides with a global your extension relies on.
122
+ * @property {string | string[]} [libraryExport="default"] Which export of the
123
+ * entry module is the extension. Defaults to the default export; pass a named
124
+ * export (or a path like `['nested', 'Extension']`) to use something else.
125
+ */ /**
126
+ * Webpack / Rspack plugin that turns a normal multi-module bundle into a
127
+ * single-file TurboWarp unsandboxed extension.
128
+ *
129
+ * Structurally compatible with both `webpack.WebpackPluginInstance` and
130
+ * `@rspack/core`'s `RspackPluginInstance`. The `compiler` parameter is typed
131
+ * loosely (`any`) on purpose so the published types don't force a dependency on
132
+ * either bundler — pick whichever one you build with.
133
+ */ class TurboWarpExtensionPlugin {
134
+ /** @param {TurboWarpExtensionPluginOptions} [options] */ constructor(options = {}){
135
+ /** @type {Required<TurboWarpExtensionPluginOptions>} */ this.options = {
136
+ register: true,
137
+ unsandboxed: false,
138
+ metadata: null,
139
+ inlineAssets: true,
140
+ name: undefined,
141
+ varName: '__turbowarpExtension__',
142
+ libraryExport: 'default',
143
+ ...options
144
+ };
145
+ // Fall back to the metadata name so the guard message reads naturally
146
+ // without having to repeat the name in two places.
147
+ this.options.name = this.options.name ?? this.options.metadata?.name ?? 'This extension';
148
+ }
149
+ /** @param {any} compiler A webpack or Rspack `Compiler`. */ apply(compiler) {
150
+ // `compiler.webpack` is the bundler's own API namespace. It exists on both
151
+ // webpack 5 and Rspack, so the plugin never has to import (or even depend
152
+ // on) either one directly.
153
+ const webpack = /** @type {any} */ compiler.webpack;
154
+ const { Compilation, sources, library } = webpack;
155
+ const { ConcatSource } = sources;
156
+ const output = compiler.options.output;
157
+ // Shape the bundle so the entry's export is reachable: a single
158
+ // self-executing file that assigns the chosen export to a local `var`,
159
+ // which the IIFE wrapper below reads and registers.
160
+ output.iife = true;
161
+ output.library = {
162
+ type: 'var',
163
+ name: this.options.varName,
164
+ export: this.options.libraryExport
165
+ };
166
+ // `EnableLibraryPlugin` is auto-applied only for library types declared in
167
+ // the *initial* config. We set the type programmatically here, so we have
168
+ // to enable support for it ourselves.
169
+ new library.EnableLibraryPlugin('var').apply(compiler);
170
+ // Inline asset imports as base64 `data:` URIs so `import icon from
171
+ // './icon.svg'` yields a string usable as `menuIconURI` — and so nothing is
172
+ // emitted as a separate file (the extension must be self-contained).
173
+ if (this.options.inlineAssets) {
174
+ const test = this.options.inlineAssets instanceof RegExp ? this.options.inlineAssets : DEFAULT_ASSET_PATTERN;
175
+ const module = compiler.options.module || (compiler.options.module = {});
176
+ const rules = module.rules || (module.rules = []);
177
+ rules.push({
178
+ test,
179
+ type: 'asset/inline'
180
+ });
181
+ }
182
+ const prefix = buildPrefix(this.options);
183
+ const suffix = buildSuffix(this.options);
184
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation)=>{
185
+ compilation.hooks.processAssets.tap({
186
+ name: PLUGIN_NAME,
187
+ // Run after minification (OPTIMIZE_SIZE) but before hashing
188
+ // (OPTIMIZE_HASH) so [contenthash] filenames stay correct.
189
+ stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE
190
+ }, ()=>{
191
+ for (const file of entryJsFiles(compilation)){
192
+ compilation.updateAsset(file, (old)=>new ConcatSource(prefix, '\n', old, '\n', suffix));
193
+ }
194
+ });
195
+ });
196
+ }
197
+ }
198
+ /**
199
+ * The registry metadata header (`// Name:` / `// ID:` / …), in the order the
200
+ * gallery conventionally lists them, followed by the opening of the TurboWarp
201
+ * IIFE template and an optional unsandboxed guard.
202
+ *
203
+ * @param {Required<TurboWarpExtensionPluginOptions>} options
204
+ * @returns {string}
205
+ */ function buildPrefix(options) {
206
+ const lines = [];
207
+ const header = buildMetadataHeader(options.metadata);
208
+ if (header) lines.push(header, ''); // blank line between header and code
209
+ lines.push('(function (Scratch) {', '"use strict";');
210
+ if (options.unsandboxed) {
211
+ const message = JSON.stringify(`${options.name} must be run unsandboxed.`);
212
+ lines.push(`if (!Scratch.extensions.unsandboxed) { throw new Error(${message}); }`);
213
+ }
214
+ return lines.join('\n');
215
+ }
216
+ // Known metadata fields, paired with the exact label the gallery expects, in
217
+ // conventional order. Unlisted fields are emitted afterwards using their key
218
+ // verbatim as the label.
219
+ const METADATA_FIELDS = [
220
+ [
221
+ 'name',
222
+ 'Name'
223
+ ],
224
+ [
225
+ 'id',
226
+ 'ID'
227
+ ],
228
+ [
229
+ 'description',
230
+ 'Description'
231
+ ],
232
+ [
233
+ 'by',
234
+ 'By'
235
+ ],
236
+ [
237
+ 'original',
238
+ 'Original'
239
+ ],
240
+ [
241
+ 'license',
242
+ 'License'
243
+ ],
244
+ [
245
+ 'context',
246
+ 'Context'
247
+ ]
248
+ ];
249
+ /**
250
+ * Render registry metadata as a block of `// Key: Value` comment lines.
251
+ * Array-valued fields (e.g. `by`) produce one line per entry, and every value
252
+ * is collapsed onto a single line so it can't break out of the comment.
253
+ *
254
+ * @param {TurboWarpExtensionMetadata | null | undefined} metadata
255
+ * @returns {string} The joined comment lines, or `''` when there's nothing.
256
+ */ function buildMetadataHeader(metadata) {
257
+ if (!metadata) return '';
258
+ const lines = [];
259
+ const emit = (label, value)=>{
260
+ if (value == null) return;
261
+ for (const entry of Array.isArray(value) ? value : [
262
+ value
263
+ ]){
264
+ const text = String(entry).replace(/[\r\n]+/g, ' ').trim();
265
+ if (text) lines.push(`// ${label}: ${text}`);
266
+ }
267
+ };
268
+ const known = new Set();
269
+ for (const [key, label] of METADATA_FIELDS){
270
+ known.add(key);
271
+ if (key in metadata) emit(label, metadata[key]);
272
+ }
273
+ for (const key of Object.keys(metadata)){
274
+ if (!known.has(key)) emit(key, metadata[key]);
275
+ }
276
+ return lines.join('\n');
277
+ }
278
+ /**
279
+ * Closing of the IIFE template. When `register` is enabled, the entry's export
280
+ * is registered — instantiated first if it is a class (a function), or passed
281
+ * straight through if it is already an instance.
282
+ *
283
+ * @param {Required<TurboWarpExtensionPluginOptions>} options
284
+ * @returns {string}
285
+ */ function buildSuffix(options) {
286
+ const lines = [];
287
+ if (options.register) {
288
+ lines.push('(function () {', ` var extension = ${options.varName};`, ' Scratch.extensions.register(', ' typeof extension === "function" ? new extension() : extension', ' );', '})();');
289
+ }
290
+ lines.push('})(Scratch);');
291
+ return lines.join('\n');
292
+ }
293
+ /**
294
+ * Collect the `.js` files that make up the initial (synchronously loaded)
295
+ * entrypoints — those are what end up inside the single extension file.
296
+ *
297
+ * @param {any} compilation A webpack or Rspack `Compilation`.
298
+ * @returns {Set<string>}
299
+ */ function entryJsFiles(compilation) {
300
+ const files = new Set();
301
+ for (const entrypoint of compilation.entrypoints.values()){
302
+ for (const file of entrypoint.getFiles()){
303
+ if (file.endsWith('.js')) files.add(file);
304
+ }
305
+ }
306
+ // Fallback for unusual setups that don't surface entrypoint files: wrap
307
+ // every emitted .js asset instead.
308
+ if (files.size === 0) {
309
+ for (const name of Object.keys(compilation.assets)){
310
+ if (name.endsWith('.js')) files.add(name);
311
+ }
312
+ }
313
+ return files;
314
+ }
315
+
316
+ /* export default */ const __rspack_default_export = (TurboWarpExtensionPlugin);
317
+
318
+ __webpack_require__.d(__webpack_exports__, {
319
+ TurboWarpExtensionPlugin: () => (TurboWarpExtensionPlugin)
320
+ }, {
321
+ "default": __rspack_default_export
322
+ });
323
+
324
+ exports.TurboWarpExtensionPlugin = __webpack_exports__.TurboWarpExtensionPlugin;
325
+ exports["default"] = __webpack_exports__["default"];
326
+ for(var __rspack_i in __webpack_exports__) {
327
+ if(["TurboWarpExtensionPlugin","default"].indexOf(__rspack_i) === -1) {
328
+ exports[__rspack_i] = __webpack_exports__[__rspack_i];
329
+ }
330
+ }
331
+ Object.defineProperty(exports, '__esModule', { value: true });
@@ -0,0 +1,173 @@
1
+ export default TurboWarpExtensionPlugin;
2
+ /**
3
+ * Registry metadata. Each field becomes a `// Key: Value` comment line at the
4
+ * very top of the file — the header the
5
+ * [TurboWarp extensions gallery](https://github.com/TurboWarp/extensions)
6
+ * requires for submission:
7
+ *
8
+ * ```js
9
+ * // Name: Consoles
10
+ * // ID: sipcconsole
11
+ * // Description: Blocks that interact with the developer console.
12
+ * // By: -SIPC-
13
+ * // License: MIT
14
+ * ```
15
+ */
16
+ export type TurboWarpExtensionMetadata = {
17
+ /**
18
+ * Display name shown in the extension list.
19
+ */
20
+ name?: string | undefined;
21
+ /**
22
+ * Unique extension id. **Must match** the `id` your
23
+ * `getInfo()` returns.
24
+ */
25
+ id?: string | undefined;
26
+ /**
27
+ * One-line description for the gallery.
28
+ */
29
+ description?: string | undefined;
30
+ /**
31
+ * Author(s). Each entry becomes its own
32
+ * `// By:` line and may include a profile link, e.g.
33
+ * `"GarboMuffin <https://scratch.mit.edu/users/GarboMuffin/>"`.
34
+ */
35
+ by?: string | string[] | undefined;
36
+ /**
37
+ * Original author(s) when this is a
38
+ * derivative — one `// Original:` line each.
39
+ */
40
+ original?: string | string[] | undefined;
41
+ /**
42
+ * SPDX license id, e.g. `"MPL-2.0"`.
43
+ */
44
+ license?: string | undefined;
45
+ /**
46
+ * Extra `// Context:` line.
47
+ */
48
+ context?: string | undefined;
49
+ };
50
+ export type TurboWarpExtensionPluginOptions = {
51
+ /**
52
+ * Append a
53
+ * `Scratch.extensions.register(...)` call for the entry's chosen export. Set
54
+ * to `false` if you would rather call `register()` yourself somewhere in your
55
+ * own code (it still runs inside the IIFE, so the `Scratch` global is
56
+ * available there too).
57
+ */
58
+ register?: boolean | undefined;
59
+ /**
60
+ * Emit a guard at the top of the bundle
61
+ * that throws unless the extension is running unsandboxed
62
+ * (`Scratch.extensions.unsandboxed`). Use this for extensions that require
63
+ * direct access to the VM.
64
+ */
65
+ unsandboxed?: boolean | undefined;
66
+ /**
67
+ * Registry metadata injected
68
+ * as the `// Name:` / `// ID:` / … comment header the TurboWarp gallery reads.
69
+ * Omit it for extensions you only ever load manually.
70
+ */
71
+ metadata?: TurboWarpExtensionMetadata | undefined;
72
+ /**
73
+ * Configure the bundler so
74
+ * importing an asset inlines it as a base64 `data:` URI — the form TurboWarp
75
+ * wants for `menuIconURI` / `blockIconURI`. With this on you can
76
+ * `import iconURI from './icon.svg'` and use `iconURI` directly. Defaults to
77
+ * matching `svg`, `png`, `jpg`, `gif`, `webp`, `avif`; pass a `RegExp` to use
78
+ * your own test, or `false` to leave asset handling to your own config.
79
+ */
80
+ inlineAssets?: boolean | RegExp | undefined;
81
+ /**
82
+ * Human-readable name used in the unsandboxed-guard
83
+ * error message. Defaults to `metadata.name` when set, otherwise
84
+ * `"This extension"`.
85
+ */
86
+ name?: string | undefined;
87
+ /**
88
+ * Identifier the bundle's
89
+ * export is assigned to before registration. Only change it if it somehow
90
+ * collides with a global your extension relies on.
91
+ */
92
+ varName?: string | undefined;
93
+ /**
94
+ * Which export of the
95
+ * entry module is the extension. Defaults to the default export; pass a named
96
+ * export (or a path like `['nested', 'Extension']`) to use something else.
97
+ */
98
+ libraryExport?: string | string[] | undefined;
99
+ };
100
+ /**
101
+ * Registry metadata. Each field becomes a `// Key: Value` comment line at the
102
+ * very top of the file — the header the
103
+ * [TurboWarp extensions gallery](https://github.com/TurboWarp/extensions)
104
+ * requires for submission:
105
+ *
106
+ * ```js
107
+ * // Name: Consoles
108
+ * // ID: sipcconsole
109
+ * // Description: Blocks that interact with the developer console.
110
+ * // By: -SIPC-
111
+ * // License: MIT
112
+ * ```
113
+ *
114
+ * @typedef {object} TurboWarpExtensionMetadata
115
+ * @property {string} [name] Display name shown in the extension list.
116
+ * @property {string} [id] Unique extension id. **Must match** the `id` your
117
+ * `getInfo()` returns.
118
+ * @property {string} [description] One-line description for the gallery.
119
+ * @property {string | string[]} [by] Author(s). Each entry becomes its own
120
+ * `// By:` line and may include a profile link, e.g.
121
+ * `"GarboMuffin <https://scratch.mit.edu/users/GarboMuffin/>"`.
122
+ * @property {string | string[]} [original] Original author(s) when this is a
123
+ * derivative — one `// Original:` line each.
124
+ * @property {string} [license] SPDX license id, e.g. `"MPL-2.0"`.
125
+ * @property {string} [context] Extra `// Context:` line.
126
+ */
127
+ /**
128
+ * @typedef {object} TurboWarpExtensionPluginOptions
129
+ * @property {boolean} [register=true] Append a
130
+ * `Scratch.extensions.register(...)` call for the entry's chosen export. Set
131
+ * to `false` if you would rather call `register()` yourself somewhere in your
132
+ * own code (it still runs inside the IIFE, so the `Scratch` global is
133
+ * available there too).
134
+ * @property {boolean} [unsandboxed=false] Emit a guard at the top of the bundle
135
+ * that throws unless the extension is running unsandboxed
136
+ * (`Scratch.extensions.unsandboxed`). Use this for extensions that require
137
+ * direct access to the VM.
138
+ * @property {TurboWarpExtensionMetadata} [metadata] Registry metadata injected
139
+ * as the `// Name:` / `// ID:` / … comment header the TurboWarp gallery reads.
140
+ * Omit it for extensions you only ever load manually.
141
+ * @property {boolean | RegExp} [inlineAssets=true] Configure the bundler so
142
+ * importing an asset inlines it as a base64 `data:` URI — the form TurboWarp
143
+ * wants for `menuIconURI` / `blockIconURI`. With this on you can
144
+ * `import iconURI from './icon.svg'` and use `iconURI` directly. Defaults to
145
+ * matching `svg`, `png`, `jpg`, `gif`, `webp`, `avif`; pass a `RegExp` to use
146
+ * your own test, or `false` to leave asset handling to your own config.
147
+ * @property {string} [name] Human-readable name used in the unsandboxed-guard
148
+ * error message. Defaults to `metadata.name` when set, otherwise
149
+ * `"This extension"`.
150
+ * @property {string} [varName="__turbowarpExtension__"] Identifier the bundle's
151
+ * export is assigned to before registration. Only change it if it somehow
152
+ * collides with a global your extension relies on.
153
+ * @property {string | string[]} [libraryExport="default"] Which export of the
154
+ * entry module is the extension. Defaults to the default export; pass a named
155
+ * export (or a path like `['nested', 'Extension']`) to use something else.
156
+ */
157
+ /**
158
+ * Webpack / Rspack plugin that turns a normal multi-module bundle into a
159
+ * single-file TurboWarp unsandboxed extension.
160
+ *
161
+ * Structurally compatible with both `webpack.WebpackPluginInstance` and
162
+ * `@rspack/core`'s `RspackPluginInstance`. The `compiler` parameter is typed
163
+ * loosely (`any`) on purpose so the published types don't force a dependency on
164
+ * either bundler — pick whichever one you build with.
165
+ */
166
+ export class TurboWarpExtensionPlugin {
167
+ /** @param {TurboWarpExtensionPluginOptions} [options] */
168
+ constructor(options?: TurboWarpExtensionPluginOptions);
169
+ /** @type {Required<TurboWarpExtensionPluginOptions>} */
170
+ options: Required<TurboWarpExtensionPluginOptions>;
171
+ /** @param {any} compiler A webpack or Rspack `Compiler`. */
172
+ apply(compiler: any): void;
173
+ }