@steambrew/ttc 2.8.7 → 3.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.
package/src/transpiler.ts CHANGED
@@ -1,334 +1,303 @@
1
- import babel from '@rollup/plugin-babel';
2
- import commonjs from '@rollup/plugin-commonjs';
3
- import json from '@rollup/plugin-json';
4
- import resolve, { nodeResolve } from '@rollup/plugin-node-resolve';
5
- import replace from '@rollup/plugin-replace';
6
- import terser from '@rollup/plugin-terser';
7
- import typescript from '@rollup/plugin-typescript';
8
- import url from '@rollup/plugin-url';
9
- import { InputPluginOption, OutputBundle, OutputOptions, RollupOptions, rollup } from 'rollup';
10
- import nodePolyfills from 'rollup-plugin-polyfill-node';
11
- import { minify_sync } from 'terser';
12
-
13
- import scss from 'rollup-plugin-scss';
14
- import * as sass from 'sass';
15
-
16
- import chalk from 'chalk';
17
- import fs from 'fs';
18
- import { Logger } from './logger';
19
-
20
- import dotenv from 'dotenv';
21
- import injectProcessEnv from 'rollup-plugin-inject-process-env';
22
- import { ExecutePluginModule, InitializePlugins } from './plugin-api';
23
- import constSysfsExpr from './static-embed';
24
-
25
- const envConfig = dotenv.config().parsed || {};
26
-
27
- if (envConfig) {
28
- Logger.Info('envVars', 'Processing ' + Object.keys(envConfig).length + ' environment variables... ' + chalk.green.bold('okay'));
29
- }
30
-
31
- const envVars = Object.keys(envConfig).reduce((acc: any, key) => {
32
- acc[key] = envConfig[key];
33
- return acc;
34
- }, {});
35
-
36
- declare global {
37
- interface Window {
38
- PLUGIN_LIST: any;
39
- MILLENNIUM_PLUGIN_SETTINGS_STORE: any;
40
- }
41
- }
42
-
43
- declare const pluginName: string, millennium_main: any, MILLENNIUM_BACKEND_IPC: any, MILLENNIUM_IS_CLIENT_MODULE: boolean;
44
-
45
- enum ComponentType {
46
- Plugin,
47
- Webkit,
48
- }
49
-
50
- export interface TranspilerProps {
51
- bTersePlugin?: boolean;
52
- strPluginInternalName: string;
53
- }
54
-
55
- declare const MILLENNIUM_API: {
56
- callable: (fn: Function, route: string) => any;
57
- __INTERNAL_CALL_WEBKIT_METHOD__: (pluginName: string, methodName: string, kwargs: any) => any;
58
- };
59
-
60
- declare const __call_server_method__: (methodName: string, kwargs: any) => any;
61
- const WrappedCallServerMethod = 'const __call_server_method__ = (methodName, kwargs) => Millennium.callServerMethod(pluginName, methodName, kwargs)';
62
-
63
- function __wrapped_callable__(route: string) {
64
- if (route.startsWith('webkit:')) {
65
- return MILLENNIUM_API.callable(
66
- (methodName: string, kwargs: any) => MILLENNIUM_API.__INTERNAL_CALL_WEBKIT_METHOD__(pluginName, methodName, kwargs),
67
- route.replace(/^webkit:/, ''),
68
- );
69
- }
70
-
71
- return MILLENNIUM_API.callable(__call_server_method__, route);
72
- }
73
-
74
- const ConstructFunctions = (parts: string[]): string => {
75
- return parts.join('\n');
76
- };
77
-
78
- function generate(code: string) {
79
- /** Wrap it in a proxy */
80
- return `let PluginEntryPointMain = function() { ${code} return millennium_main; };`;
81
- }
82
-
83
- function InsertMillennium(type: ComponentType, props: TranspilerProps): InputPluginOption {
84
- const generateBundle = (_: unknown, bundle: OutputBundle) => {
85
- for (const fileName in bundle) {
86
- if (bundle[fileName].type != 'chunk') {
87
- continue;
88
- }
89
-
90
- Logger.Info('millenniumAPI', 'Bundling into ' + ComponentType[type] + ' module... ' + chalk.green.bold('okay'));
91
-
92
- let code = ConstructFunctions([
93
- `const MILLENNIUM_IS_CLIENT_MODULE = ${type === ComponentType.Plugin ? 'true' : 'false'};`,
94
- `const pluginName = "${props.strPluginInternalName}";`,
95
- InitializePlugins.toString(),
96
- InitializePlugins.name + '()',
97
- WrappedCallServerMethod,
98
- __wrapped_callable__.toString(),
99
- generate(bundle[fileName].code),
100
- ExecutePluginModule.toString(),
101
- ExecutePluginModule.name + '()',
102
- ]);
103
-
104
- if (props.bTersePlugin) {
105
- code = minify_sync(code).code ?? code;
106
- }
107
-
108
- bundle[fileName].code = code;
109
- }
110
- };
111
-
112
- return { name: String(), generateBundle };
113
- }
114
-
115
- async function GetCustomUserPlugins() {
116
- const ttcConfigPath = new URL(`file://${process.cwd().replace(/\\/g, '/')}/ttc.config.mjs`).href;
117
-
118
- if (fs.existsSync('./ttc.config.mjs')) {
119
- const { MillenniumCompilerPlugins } = await import(ttcConfigPath);
120
-
121
- Logger.Info('millenniumAPI', 'Loading custom plugins from ttc.config.mjs... ' + chalk.green.bold('okay'));
122
- return MillenniumCompilerPlugins;
123
- }
124
-
125
- return [];
126
- }
127
-
128
- async function MergePluginList(plugins: any[]) {
129
- const customPlugins = await GetCustomUserPlugins();
130
-
131
- // Filter out custom plugins that have the same name as input plugins
132
- const filteredCustomPlugins = customPlugins.filter((customPlugin: any) => !plugins.some((plugin: any) => plugin.name === customPlugin.name));
133
-
134
- // Merge input plugins with the filtered custom plugins
135
- return [...plugins, ...filteredCustomPlugins];
136
- }
137
-
138
- async function GetPluginComponents(pluginJson: any, props: TranspilerProps): Promise<InputPluginOption[]> {
139
- let tsConfigPath = '';
140
- const frontendDir = GetFrontEndDirectory(pluginJson);
141
-
142
- if (frontendDir === '.' || frontendDir === './') {
143
- tsConfigPath = './tsconfig.json';
144
- } else {
145
- tsConfigPath = `./${frontendDir}/tsconfig.json`;
146
- }
147
-
148
- if (!fs.existsSync(tsConfigPath)) {
149
- tsConfigPath = './tsconfig.json';
150
- }
151
-
152
- Logger.Info('millenniumAPI', 'Loading tsconfig from ' + chalk.cyan.bold(tsConfigPath) + '... ' + chalk.green.bold('okay'));
153
-
154
- let pluginList = [
155
- typescript({
156
- tsconfig: tsConfigPath,
157
- compilerOptions: {
158
- outDir: undefined,
159
- },
160
- }),
161
- url({
162
- include: ['**/*.gif', '**/*.webm', '**/*.svg'], // Add all non-JS assets you use
163
- limit: 0, // Set to 0 to always copy the file instead of inlining as base64
164
- fileName: '[hash][extname]', // Optional: custom output naming
165
- }),
166
- InsertMillennium(ComponentType.Plugin, props),
167
- nodeResolve({
168
- browser: true,
169
- }),
170
- commonjs(),
171
- nodePolyfills(),
172
- scss({
173
- output: false,
174
- outputStyle: 'compressed',
175
- sourceMap: false,
176
- watch: 'src/styles',
177
- sass: sass,
178
- }),
179
- json(),
180
- constSysfsExpr(),
181
- replace({
182
- delimiters: ['', ''],
183
- preventAssignment: true,
184
- 'process.env.NODE_ENV': JSON.stringify('production'),
185
- 'Millennium.callServerMethod': `__call_server_method__`,
186
- 'client.callable': `__wrapped_callable__`,
187
- 'client.pluginSelf': 'window.PLUGIN_LIST[pluginName]',
188
- 'client.Millennium.exposeObj(': 'client.Millennium.exposeObj(exports, ',
189
- 'client.BindPluginSettings()': 'client.BindPluginSettings(pluginName)',
190
- }),
191
- ];
192
-
193
- if (envVars.length > 0) {
194
- pluginList.push(injectProcessEnv(envVars));
195
- }
196
-
197
- if (props.bTersePlugin) {
198
- pluginList.push(terser());
199
- }
200
-
201
- return pluginList;
202
- }
203
-
204
- async function GetWebkitPluginComponents(props: TranspilerProps) {
205
- let pluginList = [
206
- InsertMillennium(ComponentType.Webkit, props),
207
- typescript({
208
- tsconfig: './webkit/tsconfig.json',
209
- }),
210
- url({
211
- include: ['**/*.mp4', '**/*.webm', '**/*.ogg'],
212
- limit: 0, // do NOT inline
213
- fileName: '[name][extname]',
214
- destDir: 'dist/assets', // or adjust as needed
215
- }),
216
- resolve(),
217
- commonjs(),
218
- json(),
219
- constSysfsExpr(),
220
- replace({
221
- delimiters: ['', ''],
222
- preventAssignment: true,
223
- 'Millennium.callServerMethod': `__call_server_method__`,
224
- 'webkit.callable': `__wrapped_callable__`,
225
- 'webkit.Millennium.exposeObj(': 'webkit.Millennium.exposeObj(exports, ',
226
- 'client.BindPluginSettings()': 'client.BindPluginSettings(pluginName)',
227
- }),
228
- babel({
229
- presets: ['@babel/preset-env', '@babel/preset-react'],
230
- babelHelpers: 'bundled',
231
- }),
232
- ];
233
-
234
- if (envVars.length > 0) {
235
- pluginList.push(injectProcessEnv(envVars));
236
- }
237
-
238
- pluginList = await MergePluginList(pluginList);
239
-
240
- props.bTersePlugin && pluginList.push(terser());
241
- return pluginList;
242
- }
243
-
244
- const GetFrontEndDirectory = (pluginJson: any) => {
245
- try {
246
- return pluginJson?.frontend ?? 'frontend';
247
- } catch (error) {
248
- return 'frontend';
249
- }
250
- };
251
-
252
- export const TranspilerPluginComponent = async (bIsMillennium: boolean, pluginJson: any, props: TranspilerProps) => {
253
- const frontendDir = GetFrontEndDirectory(pluginJson);
254
- console.log(chalk.greenBright.bold('config'), 'Frontend directory set to:', chalk.cyan.bold(frontendDir));
255
-
256
- const frontendPlugins = await GetPluginComponents(pluginJson, props);
257
-
258
- // Fix entry file path construction
259
- let entryFile = '';
260
- if (frontendDir === '.' || frontendDir === './' || frontendDir === '') {
261
- entryFile = './index.tsx';
262
- } else {
263
- entryFile = `./${frontendDir}/index.tsx`;
264
- }
265
-
266
- console.log(chalk.greenBright.bold('config'), 'Entry file set to:', chalk.cyan.bold(entryFile));
267
-
268
- const frontendRollupConfig: RollupOptions = {
269
- input: entryFile,
270
- plugins: frontendPlugins,
271
- context: 'window',
272
- external: (id) => {
273
- if (id === '@steambrew/webkit') {
274
- Logger.Error(
275
- 'The @steambrew/webkit module should not be included in the frontend module, use @steambrew/client instead. Please remove it from the frontend module and try again.',
276
- );
277
- process.exit(1);
278
- }
279
-
280
- return id === '@steambrew/client' || id === 'react' || id === 'react-dom' || id === 'react-dom/client' || id === 'react/jsx-runtime';
281
- },
282
- output: {
283
- name: 'millennium_main',
284
- file: bIsMillennium ? '../../build/frontend.bin' : '.millennium/Dist/index.js',
285
- globals: {
286
- react: 'window.SP_REACT',
287
- 'react-dom': 'window.SP_REACTDOM',
288
- 'react-dom/client': 'window.SP_REACTDOM',
289
- 'react/jsx-runtime': 'SP_JSX_FACTORY',
290
- '@steambrew/client': 'window.MILLENNIUM_API',
291
- },
292
- exports: 'named',
293
- format: 'iife',
294
- },
295
- };
296
-
297
- try {
298
- await (await rollup(frontendRollupConfig)).write(frontendRollupConfig.output as OutputOptions);
299
-
300
- if (fs.existsSync(`./webkit/index.tsx`)) {
301
- const webkitRollupConfig: RollupOptions = {
302
- input: `./webkit/index.tsx`,
303
- plugins: await GetWebkitPluginComponents(props),
304
- context: 'window',
305
- external: (id) => {
306
- if (id === '@steambrew/client') {
307
- Logger.Error(
308
- 'The @steambrew/client module should not be included in the webkit module, use @steambrew/webkit instead. Please remove it from the webkit module and try again.',
309
- );
310
- process.exit(1);
311
- }
312
-
313
- return id === '@steambrew/webkit';
314
- },
315
- output: {
316
- name: 'millennium_main',
317
- file: '.millennium/Dist/webkit.js',
318
- exports: 'named',
319
- format: 'iife',
320
- globals: {
321
- '@steambrew/webkit': 'window.MILLENNIUM_API',
322
- },
323
- },
324
- };
325
-
326
- await (await rollup(webkitRollupConfig)).write(webkitRollupConfig.output as OutputOptions);
327
- }
328
-
329
- Logger.Info('build', 'Succeeded passing all tests in', Number((performance.now() - global.PerfStartTime).toFixed(3)), 'ms elapsed.');
330
- } catch (exception) {
331
- Logger.Error('error', 'Build failed!', exception);
332
- process.exit(1);
333
- }
334
- };
1
+ import babel from '@rollup/plugin-babel';
2
+ import commonjs from '@rollup/plugin-commonjs';
3
+ import json from '@rollup/plugin-json';
4
+ import resolve, { nodeResolve } from '@rollup/plugin-node-resolve';
5
+ import replace from '@rollup/plugin-replace';
6
+ import terser from '@rollup/plugin-terser';
7
+ import typescript from '@rollup/plugin-typescript';
8
+ import esbuild from 'rollup-plugin-esbuild';
9
+ import url from '@rollup/plugin-url';
10
+ import nodePolyfills from 'rollup-plugin-polyfill-node';
11
+ import { InputPluginOption, OutputBundle, OutputOptions, Plugin, RollupOptions, rollup } from 'rollup';
12
+ import { minify_sync } from 'terser';
13
+ import scss from 'rollup-plugin-scss';
14
+ import * as sass from 'sass';
15
+ import fs from 'fs';
16
+ import path from 'path';
17
+ import { pathToFileURL } from 'url';
18
+ import dotenv from 'dotenv';
19
+ import injectProcessEnv from 'rollup-plugin-inject-process-env';
20
+ import { ExecutePluginModule, InitializePlugins } from './plugin-api';
21
+ import { Logger } from './logger';
22
+ import constSysfsExpr from './static-embed';
23
+
24
+ const env = dotenv.config().parsed ?? {};
25
+
26
+ declare global {
27
+ interface Window {
28
+ PLUGIN_LIST: any;
29
+ MILLENNIUM_PLUGIN_SETTINGS_STORE: any;
30
+ }
31
+ }
32
+
33
+ declare const pluginName: string, millennium_main: any, MILLENNIUM_BACKEND_IPC: any, MILLENNIUM_IS_CLIENT_MODULE: boolean;
34
+
35
+ declare const MILLENNIUM_API: {
36
+ callable: (fn: Function, route: string) => any;
37
+ __INTERNAL_CALL_WEBKIT_METHOD__: (pluginName: string, methodName: string, kwargs: any) => any;
38
+ };
39
+
40
+ declare const __call_server_method__: (methodName: string, kwargs: any) => any;
41
+
42
+ export interface TranspilerProps {
43
+ minify: boolean;
44
+ pluginName: string;
45
+ }
46
+
47
+ enum BuildTarget {
48
+ Plugin,
49
+ Webkit,
50
+ }
51
+
52
+ const kCallServerMethod = 'const __call_server_method__ = (methodName, kwargs) => Millennium.callServerMethod(pluginName, methodName, kwargs)';
53
+
54
+ function __wrapped_callable__(route: string) {
55
+ if (route.startsWith('webkit:')) {
56
+ return MILLENNIUM_API.callable(
57
+ (methodName: string, kwargs: any) => MILLENNIUM_API.__INTERNAL_CALL_WEBKIT_METHOD__(pluginName, methodName, kwargs),
58
+ route.replace(/^webkit:/, ''),
59
+ );
60
+ }
61
+
62
+ return MILLENNIUM_API.callable(__call_server_method__, route);
63
+ }
64
+
65
+ function wrapEntryPoint(code: string): string {
66
+ return `let PluginEntryPointMain = function() { ${code} return millennium_main; };`;
67
+ }
68
+
69
+ function insertMillennium(target: BuildTarget, props: TranspilerProps): InputPluginOption {
70
+ return {
71
+ name: '',
72
+ generateBundle(_: unknown, bundle: OutputBundle) {
73
+ for (const fileName in bundle) {
74
+ const chunk = bundle[fileName];
75
+ if (chunk.type !== 'chunk') continue;
76
+
77
+ let code = `\
78
+ const MILLENNIUM_IS_CLIENT_MODULE = ${target === BuildTarget.Plugin};
79
+ const pluginName = "${props.pluginName}";
80
+ ${InitializePlugins.toString()}
81
+ ${InitializePlugins.name}()
82
+ ${kCallServerMethod}
83
+ ${__wrapped_callable__.toString()}
84
+ ${wrapEntryPoint(chunk.code)}
85
+ ${ExecutePluginModule.toString()}
86
+ ${ExecutePluginModule.name}()`;
87
+
88
+ if (props.minify) {
89
+ code = minify_sync(code).code ?? code;
90
+ }
91
+
92
+ chunk.code = code;
93
+ }
94
+ },
95
+ };
96
+ }
97
+
98
+ function getFrontendDir(pluginJson: any): string {
99
+ return pluginJson?.frontend ?? 'frontend';
100
+ }
101
+
102
+ function resolveEntryFile(frontendDir: string): string {
103
+ return frontendDir === '.' || frontendDir === './' || frontendDir === '' ? './index.tsx' : `./${frontendDir}/index.tsx`;
104
+ }
105
+
106
+ function resolveTsConfig(frontendDir: string): string {
107
+ if (frontendDir === '.' || frontendDir === './') return './tsconfig.json';
108
+ const candidate = `./${frontendDir}/tsconfig.json`;
109
+ return fs.existsSync(candidate) ? candidate : './tsconfig.json';
110
+ }
111
+
112
+ async function withUserPlugins(plugins: InputPluginOption[]): Promise<InputPluginOption[]> {
113
+ if (!fs.existsSync('./ttc.config.mjs')) return plugins;
114
+ const configUrl = pathToFileURL(path.resolve('./ttc.config.mjs')).href;
115
+ const { MillenniumCompilerPlugins } = await import(configUrl);
116
+ const deduped = (MillenniumCompilerPlugins as Plugin<any>[]).filter((custom) => !plugins.some((p) => (p as Plugin<any>)?.name === custom?.name));
117
+ return [...plugins, ...deduped];
118
+ }
119
+
120
+ function logLocation(log: { loc?: { file?: string; line: number; column: number }; id?: string }): string | undefined {
121
+ const file = log.loc?.file ?? log.id;
122
+ if (!file || !log.loc) return undefined;
123
+ return `${path.relative(process.cwd(), file)}:${log.loc.line}:${log.loc.column}`;
124
+ }
125
+
126
+ function stripPluginPrefix(message: string): string {
127
+ message = message.replace(/^\[plugin [^\]]+\]\s*/, '');
128
+ message = message.replace(/^\S[^(]*\(\d+:\d+\):\s*/, '');
129
+ message = message.replace(/^@?[\w-]+\/[\w-]+\s+/, '');
130
+ return message;
131
+ }
132
+
133
+ abstract class MillenniumBuild {
134
+ protected abstract readonly externals: ReadonlySet<string>;
135
+ protected abstract readonly forbidden: ReadonlyMap<string, string>;
136
+ protected abstract plugins(sysfsPlugin: InputPluginOption): InputPluginOption[] | Promise<InputPluginOption[]>;
137
+ protected abstract output(isMillennium: boolean): OutputOptions;
138
+
139
+ protected isExternal(id: string): boolean {
140
+ const hint = this.forbidden.get(id);
141
+ if (hint) {
142
+ Logger.error(`${id} cannot be used here; ${hint}`);
143
+ process.exit(1);
144
+ }
145
+ return this.externals.has(id);
146
+ }
147
+
148
+ async build(input: string, sysfsPlugin: InputPluginOption, isMillennium: boolean): Promise<void> {
149
+ let hasErrors = false;
150
+
151
+ const config: RollupOptions = {
152
+ input,
153
+ plugins: await this.plugins(sysfsPlugin),
154
+ onwarn: (warning) => {
155
+ const msg = stripPluginPrefix(warning.message);
156
+ const loc = logLocation(warning);
157
+ if (warning.plugin === 'typescript') {
158
+ Logger.error(msg, loc);
159
+ hasErrors = true;
160
+ } else {
161
+ Logger.warn(msg, loc);
162
+ }
163
+ },
164
+ context: 'window',
165
+ external: (id) => this.isExternal(id),
166
+ output: this.output(isMillennium),
167
+ };
168
+
169
+ await (await rollup(config)).write(config.output as OutputOptions);
170
+
171
+ if (hasErrors) process.exit(1);
172
+ }
173
+ }
174
+
175
+ class FrontendBuild extends MillenniumBuild {
176
+ protected readonly externals = new Set(['@steambrew/client', 'react', 'react-dom', 'react-dom/client', 'react/jsx-runtime']);
177
+ protected readonly forbidden = new Map([['@steambrew/webkit', 'use @steambrew/client in the frontend module']]);
178
+
179
+ constructor(
180
+ private readonly frontendDir: string,
181
+ private readonly props: TranspilerProps,
182
+ ) {
183
+ super();
184
+ }
185
+
186
+ protected plugins(sysfsPlugin: InputPluginOption): InputPluginOption[] {
187
+ const tsPlugin = this.props.minify
188
+ ? typescript({ tsconfig: resolveTsConfig(this.frontendDir), compilerOptions: { outDir: undefined } })
189
+ : esbuild({ tsconfig: resolveTsConfig(this.frontendDir) });
190
+
191
+ return [
192
+ tsPlugin,
193
+ url({ include: ['**/*.gif', '**/*.webm', '**/*.svg'], limit: 0, fileName: '[hash][extname]' }),
194
+ insertMillennium(BuildTarget.Plugin, this.props),
195
+ nodeResolve({ browser: true }),
196
+ commonjs(),
197
+ nodePolyfills(),
198
+ scss({ output: false, outputStyle: 'compressed', sourceMap: false, watch: 'src/styles', sass }),
199
+ json(),
200
+ sysfsPlugin,
201
+ replace({
202
+ delimiters: ['', ''],
203
+ preventAssignment: true,
204
+ 'process.env.NODE_ENV': JSON.stringify('production'),
205
+ 'Millennium.callServerMethod': '__call_server_method__',
206
+ 'client.callable': '__wrapped_callable__',
207
+ 'client.pluginSelf': 'window.PLUGIN_LIST[pluginName]',
208
+ 'client.Millennium.exposeObj(': 'client.Millennium.exposeObj(exports, ',
209
+ 'client.BindPluginSettings()': 'client.BindPluginSettings(pluginName)',
210
+ }),
211
+ ...(Object.keys(env).length > 0 ? [injectProcessEnv(env)] : []),
212
+ ...(this.props.minify ? [terser()] : []),
213
+ ];
214
+ }
215
+
216
+ protected output(isMillennium: boolean): OutputOptions {
217
+ return {
218
+ name: 'millennium_main',
219
+ file: isMillennium ? '../../build/frontend.bin' : '.millennium/Dist/index.js',
220
+ globals: {
221
+ react: 'window.SP_REACT',
222
+ 'react-dom': 'window.SP_REACTDOM',
223
+ 'react-dom/client': 'window.SP_REACTDOM',
224
+ 'react/jsx-runtime': 'SP_JSX_FACTORY',
225
+ '@steambrew/client': 'window.MILLENNIUM_API',
226
+ },
227
+ exports: 'named',
228
+ format: 'iife',
229
+ };
230
+ }
231
+ }
232
+
233
+ class WebkitBuild extends MillenniumBuild {
234
+ protected readonly externals = new Set(['@steambrew/webkit']);
235
+ protected readonly forbidden = new Map([['@steambrew/client', 'use @steambrew/webkit in the webkit module']]);
236
+
237
+ constructor(private readonly props: TranspilerProps) {
238
+ super();
239
+ }
240
+
241
+ protected async plugins(sysfsPlugin: InputPluginOption): Promise<InputPluginOption[]> {
242
+ const tsPlugin = this.props.minify
243
+ ? typescript({ tsconfig: './webkit/tsconfig.json' })
244
+ : esbuild({ tsconfig: './webkit/tsconfig.json' });
245
+
246
+ const base: InputPluginOption[] = [
247
+ insertMillennium(BuildTarget.Webkit, this.props),
248
+ tsPlugin,
249
+ url({ include: ['**/*.mp4', '**/*.webm', '**/*.ogg'], limit: 0, fileName: '[name][extname]', destDir: 'dist/assets' }),
250
+ resolve(),
251
+ commonjs(),
252
+ json(),
253
+ sysfsPlugin,
254
+ replace({
255
+ delimiters: ['', ''],
256
+ preventAssignment: true,
257
+ 'Millennium.callServerMethod': '__call_server_method__',
258
+ 'webkit.callable': '__wrapped_callable__',
259
+ 'webkit.Millennium.exposeObj(': 'webkit.Millennium.exposeObj(exports, ',
260
+ 'client.BindPluginSettings()': 'client.BindPluginSettings(pluginName)',
261
+ }),
262
+ babel({ presets: ['@babel/preset-env', '@babel/preset-react'], babelHelpers: 'bundled' }),
263
+ ...(Object.keys(env).length > 0 ? [injectProcessEnv(env)] : []),
264
+ ];
265
+
266
+ const merged = await withUserPlugins(base);
267
+ return this.props.minify ? [...merged, terser()] : merged;
268
+ }
269
+
270
+ protected output(_isMillennium: boolean): OutputOptions {
271
+ return {
272
+ name: 'millennium_main',
273
+ file: '.millennium/Dist/webkit.js',
274
+ exports: 'named',
275
+ format: 'iife',
276
+ globals: { '@steambrew/webkit': 'window.MILLENNIUM_API' },
277
+ };
278
+ }
279
+ }
280
+
281
+ export const TranspilerPluginComponent = async (isMillennium: boolean, pluginJson: any, props: TranspilerProps) => {
282
+ const webkitDir = './webkit/index.tsx';
283
+ const frontendDir = getFrontendDir(pluginJson);
284
+ const sysfs = constSysfsExpr();
285
+
286
+ try {
287
+ await new FrontendBuild(frontendDir, props).build(resolveEntryFile(frontendDir), sysfs.plugin, isMillennium);
288
+
289
+ if (fs.existsSync(webkitDir)) {
290
+ await new WebkitBuild(props).build(webkitDir, sysfs.plugin, isMillennium);
291
+ }
292
+
293
+ Logger.done({
294
+ elapsedMs: performance.now() - global.PerfStartTime,
295
+ buildType: props.minify ? 'prod' : 'dev',
296
+ sysfsCount: sysfs.getCount() || undefined,
297
+ envCount: Object.keys(env).length || undefined,
298
+ });
299
+ } catch (exception: any) {
300
+ Logger.error(stripPluginPrefix(exception?.message ?? String(exception)), logLocation(exception));
301
+ process.exit(1);
302
+ }
303
+ };