From d7f73c225c28916f61e40ab9eb260bed4e8901e3 Mon Sep 17 00:00:00 2001 From: Daniel Kuschny Date: Sat, 15 Jun 2024 17:43:36 +0200 Subject: [PATCH 1/2] fix(webpack): Use webpack instance passed to plugin (#1539) --- rollup.config.webpack.ts | 10 - src/webpack/AlphaTabAudioWorklet.ts | 57 ++-- src/webpack/AlphaTabWebPackPlugin.ts | 294 +++++++++++++----- src/webpack/AlphaTabWebWorker.ts | 61 ++-- src/webpack/AlphaTabWebWorkerDependency.ts | 105 +++++++ src/webpack/AlphaTabWorkerDependency.ts | 104 ------- src/webpack/AlphaTabWorkerRuntimeModule.ts | 128 ++++---- src/webpack/AlphaTabWorkletDependency.ts | 211 +++++++------ .../AlphaTabWorkletStartRuntimeModule.ts | 106 ++++--- src/webpack/Utils.ts | 129 +++++--- 10 files changed, 711 insertions(+), 494 deletions(-) create mode 100644 src/webpack/AlphaTabWebWorkerDependency.ts delete mode 100644 src/webpack/AlphaTabWorkerDependency.ts diff --git a/rollup.config.webpack.ts b/rollup.config.webpack.ts index e95c778f5..02f50b56f 100644 --- a/rollup.config.webpack.ts +++ b/rollup.config.webpack.ts @@ -49,11 +49,6 @@ export default function webpack(isWatch: boolean, bundlePlugins: Plugin[]): Roll ], external: [ 'webpack', - 'webpack/lib/ModuleTypeConstants', - 'webpack/lib/util/makeSerializable', - 'webpack/lib/util/identifier', - 'webpack/lib/javascript/EnableChunkLoadingPlugin', - 'webpack/lib/dependencies/WorkerDependency', 'fs', 'path' ], @@ -75,11 +70,6 @@ export default function webpack(isWatch: boolean, bundlePlugins: Plugin[]): Roll ], external: [ 'webpack', - 'webpack/lib/ModuleTypeConstants', - 'webpack/lib/util/makeSerializable', - 'webpack/lib/util/identifier', - 'webpack/lib/javascript/EnableChunkLoadingPlugin', - 'webpack/lib/dependencies/WorkerDependency', 'fs', 'path' ], diff --git a/src/webpack/AlphaTabAudioWorklet.ts b/src/webpack/AlphaTabAudioWorklet.ts index b33949fa0..f744aef77 100644 --- a/src/webpack/AlphaTabAudioWorklet.ts +++ b/src/webpack/AlphaTabAudioWorklet.ts @@ -1,40 +1,37 @@ /**@target web */ -import webpack from 'webpack' -import { type VariableDeclarator, type Identifier, type Expression, type CallExpression } from 'estree' +import { type VariableDeclarator, type Identifier, type Expression, type CallExpression } from 'estree'; import { AlphaTabWebPackPluginOptions } from './AlphaTabWebPackPluginOptions'; -import { AlphaTabWorkletDependency } from './AlphaTabWorkletDependency'; -import { getWorkerRuntime, parseModuleUrl, tapJavaScript } from './Utils'; +import { getWorkerRuntime, parseModuleUrl, tapJavaScript, webPackWithAlphaTab, webpackTypes } from './Utils'; -const AlphaTabWorkletSpecifierTag = Symbol("alphatab worklet specifier tag"); -const workletIndexMap = new WeakMap(); +const AlphaTabWorkletSpecifierTag = Symbol('alphatab worklet specifier tag'); +const workletIndexMap = new WeakMap(); /** * Configures the Audio Worklet aspects within webpack. * The counterpart which this plugin detects sits in alphaTab.main.ts - * @param pluginName - * @param options - * @param compiler - * @param compilation - * @param normalModuleFactory - * @param cachedContextify - * @returns + * @param pluginName + * @param options + * @param compiler + * @param compilation + * @param normalModuleFactory + * @param cachedContextify + * @returns */ -export function configureAudioWorklet(pluginName: string, +export function configureAudioWorklet( + webPackWithAlphaTab: webPackWithAlphaTab, + pluginName: string, options: AlphaTabWebPackPluginOptions, - compiler: webpack.Compiler, compilation: webpack.Compilation, normalModuleFactory: any, cachedContextify: (s: string) => string) { + compiler: webpackTypes.Compiler, + compilation: webpackTypes.Compilation, + normalModuleFactory: any, + cachedContextify: (s: string) => string +) { if (options.audioWorklets === false) { return; } - compilation.dependencyFactories.set( - AlphaTabWorkletDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - AlphaTabWorkletDependency, - new AlphaTabWorkletDependency.Template() - ); + webPackWithAlphaTab.alphaTab.registerWorkletDependency(compilation, normalModuleFactory); const handleAlphaTabWorklet = (parser: any, expr: CallExpression) => { const [arg1] = expr.arguments; @@ -49,7 +46,7 @@ export function configureAudioWorklet(pluginName: string, } const runtime = getWorkerRuntime(parser, compilation, cachedContextify, workletIndexMap); - const block = new webpack.AsyncDependenciesBlock({ + const block = new webPackWithAlphaTab.webpack.AsyncDependenciesBlock({ entryOptions: { chunkLoading: false, wasmLoading: false, @@ -59,10 +56,10 @@ export function configureAudioWorklet(pluginName: string, block.loc = expr.loc; - const workletBootstrap = new AlphaTabWorkletDependency( + const workletBootstrap = webPackWithAlphaTab.alphaTab.createWorkletDependency( url.string, [expr.range![0], expr.range![1]], - compiler.options.output.workerPublicPath, + compiler.options.output.workerPublicPath ); workletBootstrap.loc = expr.loc!; block.addDependency(workletBootstrap); @@ -72,11 +69,11 @@ export function configureAudioWorklet(pluginName: string, }; const parserPlugin = (parser: any) => { - const pattern = "alphaTabWorklet"; - const itemMembers = "addModule"; + const pattern = 'alphaTabWorklet'; + const itemMembers = 'addModule'; parser.hooks.preDeclarator.tap(pluginName, (decl: VariableDeclarator) => { - if (decl.id.type === "Identifier" && decl.id.name === pattern) { + if (decl.id.type === 'Identifier' && decl.id.name === pattern) { parser.tagVariable(decl.id.name, AlphaTabWorkletSpecifierTag); return true; } @@ -90,7 +87,7 @@ export function configureAudioWorklet(pluginName: string, parser.hooks.callMemberChain .for(AlphaTabWorkletSpecifierTag) .tap(pluginName, (expression: CallExpression, members: string[]) => { - if (itemMembers !== members.join(".")) { + if (itemMembers !== members.join('.')) { return; } return handleAlphaTabWorklet(parser, expression); diff --git a/src/webpack/AlphaTabWebPackPlugin.ts b/src/webpack/AlphaTabWebPackPlugin.ts index abac3461f..c56441e45 100644 --- a/src/webpack/AlphaTabWebPackPlugin.ts +++ b/src/webpack/AlphaTabWebPackPlugin.ts @@ -1,36 +1,168 @@ /**@target web */ -import * as fs from 'fs' -import * as path from 'path' -import webpack from 'webpack' +import * as fs from 'fs'; +import * as path from 'path'; -// webpack doesn't defined properly types for all these internals +// webpack doesn't defined properly types for all these internals // needed for the plugin -// @ts-expect-error -import { contextify } from "webpack/lib/util/identifier" +import { injectWorkerRuntimeModule } from './AlphaTabWorkerRuntimeModule'; +import { configureAudioWorklet } from './AlphaTabAudioWorklet'; +import { AlphaTabWebPackPluginOptions } from './AlphaTabWebPackPluginOptions'; +import { configureWebWorker } from './AlphaTabWebWorker'; +import { webPackWithAlphaTab, webpackTypes } from './Utils'; +import { injectWebWorkerDependency as injectWebWorkerDependency } from './AlphaTabWebWorkerDependency'; +import { injectWorkletRuntimeModule } from './AlphaTabWorkletStartRuntimeModule'; +import { injectWorkletDependency } from './AlphaTabWorkletDependency'; + +const WINDOWS_ABS_PATH_REGEXP = /^[a-zA-Z]:[\\/]/; +const WINDOWS_PATH_SEPARATOR_REGEXP = /\\/g; + +const relativePathToRequest = (relativePath: string): string => { + if (relativePath === '') return './.'; + if (relativePath === '..') return '../.'; + if (relativePath.startsWith('../')) return relativePath; + return `./${relativePath}`; +}; + +const absoluteToRequest = (context: string, maybeAbsolutePath: string): string => { + if (maybeAbsolutePath[0] === '/') { + if (maybeAbsolutePath.length > 1 && maybeAbsolutePath[maybeAbsolutePath.length - 1] === '/') { + // this 'path' is actually a regexp generated by dynamic requires. + // Don't treat it as an absolute path. + return maybeAbsolutePath; + } -// @ts-expect-error -import WorkerPlugin from 'webpack/lib/dependencies/WorkerPlugin' + const querySplitPos = maybeAbsolutePath.indexOf('?'); + let resource = querySplitPos === -1 ? maybeAbsolutePath : maybeAbsolutePath.slice(0, querySplitPos); + resource = relativePathToRequest(path.posix.relative(context, resource)); + return querySplitPos === -1 ? resource : resource + maybeAbsolutePath.slice(querySplitPos); + } -import { AlphaTabWorkerRuntimeModule } from './AlphaTabWorkerRuntimeModule' -import { AlphaTabWorkletStartRuntimeModule } from './AlphaTabWorkletStartRuntimeModule' -import { configureAudioWorklet } from './AlphaTabAudioWorklet'; -import { AlphaTabWebPackPluginOptions } from './AlphaTabWebPackPluginOptions' -import { configureWebWorker } from './AlphaTabWebWorker' + if (WINDOWS_ABS_PATH_REGEXP.test(maybeAbsolutePath)) { + const querySplitPos = maybeAbsolutePath.indexOf('?'); + let resource = querySplitPos === -1 ? maybeAbsolutePath : maybeAbsolutePath.slice(0, querySplitPos); + resource = path.win32.relative(context, resource); + if (!WINDOWS_ABS_PATH_REGEXP.test(resource)) { + resource = relativePathToRequest(resource.replace(WINDOWS_PATH_SEPARATOR_REGEXP, '/')); + } + return querySplitPos === -1 ? resource : resource + maybeAbsolutePath.slice(querySplitPos); + } + + // not an absolute path + return maybeAbsolutePath; +}; + +const _contextify = (context: string, request: string): string => { + return request + .split('!') + .map(r => absoluteToRequest(context, r)) + .join('!'); +}; + +const makeCacheableWithContext = (fn: (text: string, request: string) => string) => { + const cache = new WeakMap>>(); + + const cachedFn = (context: string, identifier: string, associatedObjectForCache?: object): string => { + if (!associatedObjectForCache) return fn(context, identifier); + + let innerCache = cache.get(associatedObjectForCache); + if (innerCache === undefined) { + innerCache = new Map(); + cache.set(associatedObjectForCache, innerCache); + } + + let cachedResult; + let innerSubCache = innerCache.get(context); + if (innerSubCache === undefined) { + innerCache.set(context, (innerSubCache = new Map())); + } else { + cachedResult = innerSubCache.get(identifier); + } + + if (cachedResult !== undefined) { + return cachedResult; + } else { + const result = fn(context, identifier); + innerSubCache.set(identifier, result); + return result; + } + }; + + cachedFn.bindContextCache = ( + context: string, + associatedObjectForCache?: object + ): ((identifier: string) => string) => { + let innerSubCache; + if (associatedObjectForCache) { + let innerCache = cache.get(associatedObjectForCache); + if (innerCache === undefined) { + innerCache = new Map(); + cache.set(associatedObjectForCache, innerCache); + } + + innerSubCache = innerCache.get(context); + if (innerSubCache === undefined) { + innerCache.set(context, (innerSubCache = new Map())); + } + } else { + innerSubCache = new Map(); + } + + const boundFn = (identifier: string): string => { + const cachedResult = innerSubCache.get(identifier); + if (cachedResult !== undefined) { + return cachedResult; + } else { + const result = fn(context, identifier); + innerSubCache.set(identifier, result); + return result; + } + }; + + return boundFn; + }; + + return cachedFn; +}; + +const contextify = makeCacheableWithContext(_contextify); export class AlphaTabWebPackPlugin { + _webPackWithAlphaTab!: webPackWithAlphaTab; options: AlphaTabWebPackPluginOptions; constructor(options?: AlphaTabWebPackPluginOptions) { this.options = options ?? {}; } - apply(compiler: webpack.Compiler) { + apply(compiler: webpackTypes.Compiler) { + // here we create all plugin related class implementations using + // the webpack instance provided to this plugin (not as global import) + // after that we use the helper and factory functions we add to webpack + const webPackWithAlphaTab = { + webpack: compiler.webpack, + alphaTab: {} as any + } satisfies webPackWithAlphaTab; + + if ('alphaTab' in compiler.webpack.util.serialization.register) { // prevent multi registration + webPackWithAlphaTab.alphaTab = compiler.webpack.util.serialization.register.alphaTab; + } else { + (compiler.webpack.util.serialization.register as any).alphaTab = webPackWithAlphaTab.alphaTab; + + injectWebWorkerDependency(webPackWithAlphaTab); + injectWorkerRuntimeModule(webPackWithAlphaTab); + + injectWorkletDependency(webPackWithAlphaTab); + injectWorkletRuntimeModule(webPackWithAlphaTab); + } + + this._webPackWithAlphaTab = webPackWithAlphaTab; + this.configureSoundFont(compiler); this.configure(compiler); } - configureSoundFont(compiler: webpack.Compiler) { + configureSoundFont(compiler: webpackTypes.Compiler) { if (this.options.assetOutputDir === false) { return; } @@ -38,42 +170,47 @@ export class AlphaTabWebPackPlugin { // register soundfont as resource compiler.options.module.rules.push({ test: /\.sf2/, - type: "asset/resource", + type: 'asset/resource' }); } - - configure(compiler: webpack.Compiler) { + configure(compiler: webpackTypes.Compiler) { const pluginName = this.constructor.name; - const cachedContextify = contextify.bindContextCache( - compiler.context, - compiler.root - ); + const cachedContextify = contextify.bindContextCache(compiler.context, compiler.root); compiler.hooks.thisCompilation.tap(pluginName, (compilation, { normalModuleFactory }) => { - - - compilation.hooks.runtimeRequirementInTree - .for(AlphaTabWorkerRuntimeModule.Key) - .tap(pluginName, (chunk: webpack.Chunk) => { - compilation.addRuntimeModule(chunk, new AlphaTabWorkerRuntimeModule()); - }); - - compilation.hooks.runtimeRequirementInTree - .for(AlphaTabWorkletStartRuntimeModule.RuntimeGlobalWorkletGetStartupChunks) - .tap(pluginName, (chunk: webpack.Chunk) => { - compilation.addRuntimeModule(chunk, new AlphaTabWorkletStartRuntimeModule()); - }); - - - configureAudioWorklet(pluginName, this.options, compiler, compilation, normalModuleFactory, cachedContextify); - configureWebWorker(pluginName, this.options, compiler, compilation, normalModuleFactory, cachedContextify); - this.configureAssetCopy(pluginName, compiler, compilation); + this._webPackWithAlphaTab.alphaTab.registerWebWorkerRuntimeModule(pluginName, compilation); + this._webPackWithAlphaTab.alphaTab.registerWorkletRuntimeModule(pluginName, compilation); + + configureAudioWorklet( + this._webPackWithAlphaTab, + pluginName, + this.options, + compiler, + compilation, + normalModuleFactory, + cachedContextify + ); + configureWebWorker( + this._webPackWithAlphaTab, + pluginName, + this.options, + compiler, + compilation, + normalModuleFactory, + cachedContextify + ); + this.configureAssetCopy(this._webPackWithAlphaTab, pluginName, compiler, compilation); }); } - configureAssetCopy(pluginName: string, compiler: webpack.Compiler, compilation: webpack.Compilation) { + configureAssetCopy( + webPackWithAlphaTab: webPackWithAlphaTab, + pluginName: string, + compiler: webpackTypes.Compiler, + compilation: webpackTypes.Compilation + ) { if (this.options.assetOutputDir === false) { return; } @@ -82,23 +219,33 @@ export class AlphaTabWebPackPlugin { compilation.hooks.processAssets.tapAsync( { name: pluginName, - stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, + stage: this._webPackWithAlphaTab.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL }, async (_, callback) => { - let alphaTabSourceDir = options.alphaTabSourceDir; if (!alphaTabSourceDir) { alphaTabSourceDir = compilation.getPath('node_modules/@coderline/alphatab/dist/'); } - if (!alphaTabSourceDir || !fs.promises.access(path.join(alphaTabSourceDir, 'alphaTab.mjs'), fs.constants.F_OK)) { - compilation.errors.push(new webpack.WebpackError('Could not find alphaTab, please ensure it is installed into node_modules or configure alphaTabSourceDir')); + if ( + !alphaTabSourceDir || + !fs.promises.access(path.join(alphaTabSourceDir, 'alphaTab.mjs'), fs.constants.F_OK) + ) { + compilation.errors.push( + new this._webPackWithAlphaTab.webpack.WebpackError( + 'Could not find alphaTab, please ensure it is installed into node_modules or configure alphaTabSourceDir' + ) + ); return; } const outputPath = (options.assetOutputDir ?? compiler.options.output.path) as string; if (!outputPath) { - compilation.errors.push(new webpack.WebpackError('Need output.path configured in application to store asset files.')); + compilation.errors.push( + new this._webPackWithAlphaTab.webpack.WebpackError( + 'Need output.path configured in application to store asset files.' + ) + ); return; } @@ -111,38 +258,37 @@ export class AlphaTabWebPackPlugin { await fs.promises.mkdir(path.join(outputPath!, subdir), { recursive: true }); - await Promise.all(files.filter(f => f.isFile()).map( - async file => { - const sourceFilename = path.join(file.path, file.name); - await fs.promises.copyFile(sourceFilename, path.join(outputPath!, subdir, file.name)); - const assetFileName = subdir + '/' + file.name; - const existingAsset = compilation.getAsset(assetFileName); - - const data = await fs.promises.readFile(sourceFilename); - const source = new compiler.webpack.sources.RawSource(data); - - if (existingAsset) { - compilation.updateAsset(assetFileName, source, { - copied: true, - sourceFilename - }); - } else { - compilation.emitAsset(assetFileName, source, { - copied: true, - sourceFilename - }); - } - } - )); + await Promise.all( + files + .filter(f => f.isFile()) + .map(async file => { + const sourceFilename = path.join(file.path, file.name); + await fs.promises.copyFile(sourceFilename, path.join(outputPath!, subdir, file.name)); + const assetFileName = subdir + '/' + file.name; + const existingAsset = compilation.getAsset(assetFileName); + + const data = await fs.promises.readFile(sourceFilename); + const source = new webPackWithAlphaTab.webpack.sources.RawSource(data); + + if (existingAsset) { + compilation.updateAsset(assetFileName, source, { + copied: true, + sourceFilename + }); + } else { + compilation.emitAsset(assetFileName, source, { + copied: true, + sourceFilename + }); + } + }) + ); } - await Promise.all([ - copyFiles("font"), - copyFiles("soundfont") - ]); + await Promise.all([copyFiles('font'), copyFiles('soundfont')]); callback(); } ); } -} \ No newline at end of file +} diff --git a/src/webpack/AlphaTabWebWorker.ts b/src/webpack/AlphaTabWebWorker.ts index 99bb98813..07c1db6ad 100644 --- a/src/webpack/AlphaTabWebWorker.ts +++ b/src/webpack/AlphaTabWebWorker.ts @@ -1,45 +1,38 @@ /**@target web */ -import webpack from 'webpack' - -import { type Expression, type CallExpression } from 'estree' +import { type Expression, type CallExpression } from 'estree'; import { AlphaTabWebPackPluginOptions } from './AlphaTabWebPackPluginOptions'; -import { getWorkerRuntime, parseModuleUrl, tapJavaScript } from './Utils'; -// @ts-expect-error -import EnableChunkLoadingPlugin from "webpack/lib/javascript/EnableChunkLoadingPlugin"; -// @ts-expect-error -import WorkerDependency from "webpack/lib/dependencies/WorkerDependency"; +import { getWorkerRuntime, parseModuleUrl, tapJavaScript, webPackWithAlphaTab, webpackTypes } from './Utils'; -const workerIndexMap = new WeakMap(); +const workerIndexMap = new WeakMap(); /** * Configures the WebWorker aspects within webpack. * The counterpart which this plugin detects sits in alphaTab.main.ts - * @param pluginName - * @param options - * @param compiler - * @param compilation - * @param normalModuleFactory - * @param cachedContextify - * @returns + * @param pluginName + * @param options + * @param compiler + * @param compilation + * @param normalModuleFactory + * @param cachedContextify + * @returns */ -export function configureWebWorker(pluginName: string, +export function configureWebWorker( + webPackWithAlphaTab: webPackWithAlphaTab, + pluginName: string, options: AlphaTabWebPackPluginOptions, - compiler: webpack.Compiler, compilation: webpack.Compilation, normalModuleFactory: any, cachedContextify: (s: string) => string) { + compiler: webpackTypes.Compiler, + compilation: webpackTypes.Compilation, + normalModuleFactory: any, + cachedContextify: (s: string) => string +) { if (options.audioWorklets === false) { return; } - compilation.dependencyFactories.set( - WorkerDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - WorkerDependency, - new WorkerDependency.Template() - ); + webPackWithAlphaTab.alphaTab.registerWebWorkerDependency(compilation, normalModuleFactory); - new EnableChunkLoadingPlugin('import-scripts').apply(compiler); + new webPackWithAlphaTab.webpack.javascript.EnableChunkLoadingPlugin('import-scripts').apply(compiler); const handleAlphaTabWorker = (parser: any, expr: CallExpression) => { const [arg1, arg2] = expr.arguments; @@ -55,7 +48,7 @@ export function configureWebWorker(pluginName: string, const runtime = getWorkerRuntime(parser, compilation, cachedContextify, workerIndexMap); - const block = new webpack.AsyncDependenciesBlock({ + const block = new webPackWithAlphaTab.webpack.AsyncDependenciesBlock({ entryOptions: { chunkLoading: 'import-scripts', wasmLoading: false, @@ -65,17 +58,17 @@ export function configureWebWorker(pluginName: string, block.loc = expr.loc; - const workletBootstrap = new WorkerDependency( + const workletBootstrap = webPackWithAlphaTab.alphaTab.createWebWorkerDependency( url.string, range, - compiler.options.output.workerPublicPath, + compiler.options.output.workerPublicPath ); workletBootstrap.loc = expr.loc!; block.addDependency(workletBootstrap); parser.state.module.addBlock(block); - const dep1 = new webpack.dependencies.ConstDependency( - `{ type: ${compilation.options.output.module ? '"module"' : "undefined"} }`, + const dep1 = new webPackWithAlphaTab.webpack.dependencies.ConstDependency( + `{ type: ${compilation.options.output.module ? '"module"' : 'undefined'} }`, arg2.range! ); dep1.loc = expr.loc!; @@ -87,7 +80,9 @@ export function configureWebWorker(pluginName: string, }; const parserPlugin = (parser: any) => { - parser.hooks.new.for("alphaTab.Environment.alphaTabWorker").tap(pluginName, (expr:CallExpression) => handleAlphaTabWorker(parser, expr)); + parser.hooks.new + .for('alphaTab.Environment.alphaTabWorker') + .tap(pluginName, (expr: CallExpression) => handleAlphaTabWorker(parser, expr)); }; tapJavaScript(normalModuleFactory, pluginName, parserPlugin); diff --git a/src/webpack/AlphaTabWebWorkerDependency.ts b/src/webpack/AlphaTabWebWorkerDependency.ts new file mode 100644 index 000000000..3f974671b --- /dev/null +++ b/src/webpack/AlphaTabWebWorkerDependency.ts @@ -0,0 +1,105 @@ +/**@target web */ + +import { + Hash, + ObjectSerializerContext, + ObjectDeserializerContext, + makeDependencySerializable, + webPackWithAlphaTab, + webpackTypes, + NormalModuleFactory +} from './Utils'; + +export function injectWebWorkerDependency(webPackWithAlphaTab: webPackWithAlphaTab) { + class AlphaTabWebWorkerDependency extends webPackWithAlphaTab.webpack.dependencies.ModuleDependency { + publicPath: string | undefined; + private _hashUpdate: string | undefined; + + constructor(request: string, range: [number, number], publicPath: string | undefined) { + super(request); + this.range = range; + this.publicPath = publicPath; + } + + override getReferencedExports() { + return webPackWithAlphaTab.webpack.Dependency.NO_EXPORTS_REFERENCED; + } + + override get type() { + return 'alphaTabWorker'; + } + + override get category() { + return 'worker'; + } + + override updateHash(hash: Hash) { + if (this._hashUpdate === undefined) { + this._hashUpdate = JSON.stringify(this.publicPath); + } + hash.update(this._hashUpdate); + } + + override serialize(context: ObjectSerializerContext) { + const { write } = context; + write(this.publicPath); + super.serialize(context as any); + } + + override deserialize(context: ObjectDeserializerContext) { + const { read } = context; + this.publicPath = read(); + super.deserialize(context as any); + } + } + + AlphaTabWebWorkerDependency.Template = class WorkerDependencyTemplate extends ( + webPackWithAlphaTab.webpack.dependencies.ModuleDependency.Template + ) { + override apply( + dependency: webpackTypes.Dependency, + source: webpackTypes.sources.ReplaceSource, + templateContext: { + chunkGraph: webpackTypes.ChunkGraph; + moduleGraph: webpackTypes.ModuleGraph; + runtimeRequirements: Set; + } + ): void { + const { chunkGraph, moduleGraph, runtimeRequirements } = templateContext; + const dep = dependency as AlphaTabWebWorkerDependency; + const block = moduleGraph.getParentBlock(dependency) as webpackTypes.AsyncDependenciesBlock; + const entrypoint = chunkGraph.getBlockChunkGroup(block) as any; + const chunk = entrypoint.getEntrypointChunk() as webpackTypes.Chunk; + // We use the workerPublicPath option if provided, else we fallback to the RuntimeGlobal publicPath + const workerImportBaseUrl = dep.publicPath ? `"${dep.publicPath}"` : webPackWithAlphaTab.webpack.RuntimeGlobals.publicPath; + + runtimeRequirements.add(webPackWithAlphaTab.webpack.RuntimeGlobals.publicPath); + runtimeRequirements.add(webPackWithAlphaTab.webpack.RuntimeGlobals.baseURI); + runtimeRequirements.add(webPackWithAlphaTab.webpack.RuntimeGlobals.getChunkScriptFilename); + + source.replace( + dep.range[0], + dep.range[1] - 1, + `/* worker import */ ${workerImportBaseUrl} + ${ + webPackWithAlphaTab.webpack.RuntimeGlobals.getChunkScriptFilename + }(${JSON.stringify(chunk.id)}), ${webPackWithAlphaTab.webpack.RuntimeGlobals.baseURI}` + ); + } + }; + + makeDependencySerializable(webPackWithAlphaTab, AlphaTabWebWorkerDependency, 'AlphaTabWebWorkerDependency'); + + webPackWithAlphaTab.alphaTab.createWebWorkerDependency = ( + request: string, + range: [number, number], + publicPath: string | undefined + ) => new AlphaTabWebWorkerDependency(request, range, publicPath); + + webPackWithAlphaTab.alphaTab.registerWebWorkerDependency = ( + compilation: webpackTypes.Compilation, + normalModuleFactory: NormalModuleFactory + ) => { + compilation.dependencyFactories.set(AlphaTabWebWorkerDependency, normalModuleFactory); + compilation.dependencyTemplates.set(AlphaTabWebWorkerDependency, new AlphaTabWebWorkerDependency.Template()); + }; +} diff --git a/src/webpack/AlphaTabWorkerDependency.ts b/src/webpack/AlphaTabWorkerDependency.ts deleted file mode 100644 index 4f2cdfcf4..000000000 --- a/src/webpack/AlphaTabWorkerDependency.ts +++ /dev/null @@ -1,104 +0,0 @@ -/**@target web */ -import webpack from 'webpack' - -import { AlphaTabWorkerRuntimeModule } from './AlphaTabWorkerRuntimeModule' -import { AlphaTabWorkletStartRuntimeModule } from './AlphaTabWorkletStartRuntimeModule'; -import { Hash, ObjectDeserializerContext, ObjectSerializerContext, makeDependencySerializable } from './Utils'; - - -/** - * This module dependency injects the relevant code into a worker bootstrap script - * load chunks via importScript. - * - */ -export class AlphaTabWorkletDependency extends webpack.dependencies.ModuleDependency { - publicPath: string | undefined; - - private _hashUpdate: string | undefined; - - constructor(url: string, range: [number, number], publicPath: string | undefined) { - super(url); - this.range = range; - this.publicPath = publicPath; - } - - override get type() { - return "alphaTabWorklet"; - } - - override get category() { - return "worker"; - } - - override updateHash(hash: Hash): void { - if (this._hashUpdate === undefined) { - this._hashUpdate = JSON.stringify(this.publicPath); - } - hash.update(this._hashUpdate); - } - - override serialize(context: ObjectSerializerContext): void { - const { write } = context; - write(this.publicPath); - super.serialize(context as any); - } - - override deserialize(context: ObjectDeserializerContext): void { - const { read } = context; - this.publicPath = read(); - super.deserialize(context as any); - } -} - -AlphaTabWorkletDependency.Template = class AlphaTabWorkletDependencyTemplate extends webpack.dependencies.ModuleDependency.Template { - override apply(dependency: webpack.Dependency, source: webpack.sources.ReplaceSource, templateContext: { chunkGraph: webpack.ChunkGraph, moduleGraph: webpack.ModuleGraph, runtimeRequirements: Set }): void { - const { chunkGraph, moduleGraph, runtimeRequirements } = templateContext; - const dep = dependency as AlphaTabWorkletDependency; - - const block = moduleGraph.getParentBlock(dependency) as webpack.AsyncDependenciesBlock; - const entrypoint = chunkGraph.getBlockChunkGroup(block) as any; - - const workletImportBaseUrl = dep.publicPath - ? JSON.stringify(dep.publicPath) - : webpack.RuntimeGlobals.publicPath; - - const chunk = entrypoint.getEntrypointChunk() as webpack.Chunk; - - // worklet global scope has no 'self', need to inject it for compatibility with chunks - // some plugins like the auto public path need to right location. we pass this on from the main runtime - // some plugins rely on importScripts to be defined. - const workletInlineBootstrap = ` - globalThis.self = globalThis.self || globalThis; - globalThis.location = \${JSON.stringify(${webpack.RuntimeGlobals.baseURI})}; - globalThis.importScripts = (url) => { throw new Error("importScripts not available, dynamic loading of chunks not supported in this context", url) }; - `; - - chunkGraph.addChunkRuntimeRequirements(chunk, new Set([ - webpack.RuntimeGlobals.moduleFactories, - AlphaTabWorkerRuntimeModule.Key - ])) - runtimeRequirements.add(AlphaTabWorkletStartRuntimeModule.RuntimeGlobalWorkletGetStartupChunks); - - source.replace( - dep.range[0], - dep.range[1] - 1, - webpack.Template.asString([ - "(/* worklet bootstrap */ async function(__webpack_worklet__) {", - webpack.Template.indent([ - `await __webpack_worklet__.addModule(URL.createObjectURL(new Blob([\`${workletInlineBootstrap}\`], { type: "application/javascript; charset=utf-8" })));`, - `for (const fileName of ${AlphaTabWorkletStartRuntimeModule.RuntimeGlobalWorkletGetStartupChunks}(${chunk.id})) {`, - webpack.Template.indent([ - `await __webpack_worklet__.addModule(new URL(${workletImportBaseUrl} + fileName, ${webpack.RuntimeGlobals.baseURI}));` - ]), - "}" - ]), - `})(alphaTabWorklet)` - ]) - ); - } -} - -makeDependencySerializable( - AlphaTabWorkletDependency, - 'AlphaTabWorkletDependency' -); diff --git a/src/webpack/AlphaTabWorkerRuntimeModule.ts b/src/webpack/AlphaTabWorkerRuntimeModule.ts index e2d40ac28..98f606ddb 100644 --- a/src/webpack/AlphaTabWorkerRuntimeModule.ts +++ b/src/webpack/AlphaTabWorkerRuntimeModule.ts @@ -1,66 +1,82 @@ /**@target web */ -import webpack from 'webpack' -export class AlphaTabWorkerRuntimeModule extends webpack.RuntimeModule { - public static Key: string = "AlphaTabWorkerRuntime"; +import { isWorkerRuntime, webPackWithAlphaTab, webpackTypes } from './Utils'; - constructor() { - super("alphaTab audio worker chunk loading", webpack.RuntimeModule.STAGE_BASIC); - } +export function injectWorkerRuntimeModule(webPackWithAlphaTab: webPackWithAlphaTab) { + class AlphaTabWorkerRuntimeModule extends webPackWithAlphaTab.webpack.RuntimeModule { + public static Key: string = 'AlphaTabWorkerRuntime'; - override generate(): string | null { - const compilation = this.compilation!; - const runtimeTemplate = compilation.runtimeTemplate; - const globalObject = runtimeTemplate.globalObject; - const chunkLoadingGlobalExpr = `${globalObject}[${JSON.stringify( - compilation.outputOptions.chunkLoadingGlobal - )}]`; + constructor() { + super('alphaTab audio worker chunk loading', webPackWithAlphaTab.webpack.RuntimeModule.STAGE_BASIC); + } + override generate(): string | null { + const compilation = this.compilation!; + const runtimeTemplate = compilation.runtimeTemplate; + const globalObject = runtimeTemplate.globalObject; + const chunkLoadingGlobalExpr = `${globalObject}[${JSON.stringify( + compilation.outputOptions.chunkLoadingGlobal + )}]`; - const initialChunkIds = new Set(this.chunk!.ids); - for (const c of this.chunk!.getAllInitialChunks()) { - if (webpack.javascript.JavascriptModulesPlugin.chunkHasJs(c, this.chunkGraph!)) { - continue; - } - for (const id of c.ids!) { - initialChunkIds.add(id); + const initialChunkIds = new Set(this.chunk!.ids); + for (const c of this.chunk!.getAllInitialChunks()) { + if (webPackWithAlphaTab.webpack.javascript.JavascriptModulesPlugin.chunkHasJs(c, this.chunkGraph!)) { + continue; + } + for (const id of c.ids!) { + initialChunkIds.add(id); + } } - } - - return webpack.Template.asString([ - `if ( ! ('AudioWorkletGlobalScope' in ${globalObject}) ) { return; }`, - `const installedChunks = {`, - webpack.Template.indent( - Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 1`).join( - ",\n" - ) - ), - "};", - "// importScripts chunk loading", - `const installChunk = ${runtimeTemplate.basicFunction("data", [ - runtimeTemplate.destructureArray( - ["chunkIds", "moreModules", "runtime"], - "data" + return webPackWithAlphaTab.webpack.Template.asString([ + `if ( ! ('AudioWorkletGlobalScope' in ${globalObject}) ) { return; }`, + `const installedChunks = {`, + webPackWithAlphaTab.webpack.Template.indent( + Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 1`).join(',\n') ), - "for(const moduleId in moreModules) {", - webpack.Template.indent([ - `if(${webpack.RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, - webpack.Template.indent( - `${webpack.RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];` - ), - "}" - ]), - "}", - `if(runtime) runtime(${webpack.RuntimeGlobals.require});`, - "while(chunkIds.length)", - webpack.Template.indent("installedChunks[chunkIds.pop()] = 1;"), - "parentChunkLoadingFunction(data);" - ])};`, - `const chunkLoadingGlobal = ${chunkLoadingGlobalExpr} = ${chunkLoadingGlobalExpr} || [];`, - "const parentChunkLoadingFunction = chunkLoadingGlobal.push.bind(chunkLoadingGlobal);", - "chunkLoadingGlobal.forEach(installChunk);", - "chunkLoadingGlobal.push = installChunk;" - ]); + '};', + + '// importScripts chunk loading', + `const installChunk = ${runtimeTemplate.basicFunction('data', [ + runtimeTemplate.destructureArray(['chunkIds', 'moreModules', 'runtime'], 'data'), + 'for(const moduleId in moreModules) {', + webPackWithAlphaTab.webpack.Template.indent([ + `if(${webPackWithAlphaTab.webpack.RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, + webPackWithAlphaTab.webpack.Template.indent( + `${webPackWithAlphaTab.webpack.RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];` + ), + '}' + ]), + '}', + `if(runtime) runtime(${webPackWithAlphaTab.webpack.RuntimeGlobals.require});`, + 'while(chunkIds.length)', + webPackWithAlphaTab.webpack.Template.indent('installedChunks[chunkIds.pop()] = 1;'), + 'parentChunkLoadingFunction(data);' + ])};`, + `const chunkLoadingGlobal = ${chunkLoadingGlobalExpr} = ${chunkLoadingGlobalExpr} || [];`, + 'const parentChunkLoadingFunction = chunkLoadingGlobal.push.bind(chunkLoadingGlobal);', + 'chunkLoadingGlobal.forEach(installChunk);', + 'chunkLoadingGlobal.push = installChunk;' + ]); + } } -} \ No newline at end of file + + webPackWithAlphaTab.alphaTab.registerWebWorkerRuntimeModule = ( + pluginName: string, + compilation: webpackTypes.Compilation + ) => { + compilation.hooks.runtimeRequirementInTree + .for(AlphaTabWorkerRuntimeModule.Key) + .tap(pluginName, (chunk: webpackTypes.Chunk) => { + compilation.addRuntimeModule(chunk, new AlphaTabWorkerRuntimeModule()); + }); + + compilation.hooks.additionalChunkRuntimeRequirements.tap(pluginName, (chunk, runtimeRequirements) =>{ + if(isWorkerRuntime(chunk.runtime)) { + runtimeRequirements.add(webPackWithAlphaTab.webpack.RuntimeGlobals.moduleFactories); + runtimeRequirements.add(webPackWithAlphaTab.alphaTab.WebWorkerRuntimeModuleKey); + } + }); + }; + webPackWithAlphaTab.alphaTab.WebWorkerRuntimeModuleKey = AlphaTabWorkerRuntimeModule.Key; +} diff --git a/src/webpack/AlphaTabWorkletDependency.ts b/src/webpack/AlphaTabWorkletDependency.ts index 122a1a594..963eb9666 100644 --- a/src/webpack/AlphaTabWorkletDependency.ts +++ b/src/webpack/AlphaTabWorkletDependency.ts @@ -1,103 +1,132 @@ /**@target web */ -import webpack from 'webpack' -import { AlphaTabWorkerRuntimeModule } from './AlphaTabWorkerRuntimeModule' -import { AlphaTabWorkletStartRuntimeModule } from './AlphaTabWorkletStartRuntimeModule'; -import { Hash, ObjectDeserializerContext, ObjectSerializerContext, makeDependencySerializable } from './Utils'; - - -/** - * This module dependency injects the relevant code into a worklet bootstrap script - * to install chunks which have been added to the worklet via addModule before the bootstrap script starts. - */ -export class AlphaTabWorkletDependency extends webpack.dependencies.ModuleDependency { - publicPath: string | undefined; - - private _hashUpdate: string | undefined; - - constructor(url: string, range: [number, number], publicPath: string | undefined) { - super(url); - this.range = range; - this.publicPath = publicPath; - } +import { + Hash, + NormalModuleFactory, + ObjectDeserializerContext, + ObjectSerializerContext, + makeDependencySerializable, + webPackWithAlphaTab, + webpackTypes +} from './Utils'; + +export function injectWorkletDependency(webPackWithAlphaTab: webPackWithAlphaTab) { + /** + * This module dependency injects the relevant code into a worklet bootstrap script + * to install chunks which have been added to the worklet via addModule before the bootstrap script starts. + */ + class AlphaTabWorkletDependency extends webPackWithAlphaTab.webpack.dependencies.ModuleDependency { + publicPath: string | undefined; + + private _hashUpdate: string | undefined; + + constructor(url: string, range: [number, number], publicPath: string | undefined) { + super(url); + this.range = range; + this.publicPath = publicPath; + } - override get type() { - return "alphaTabWorklet"; - } + override get type() { + return 'alphaTabWorklet'; + } - override get category() { - return "worker"; - } + override get category() { + return 'worker'; + } - override updateHash(hash: Hash): void { - if (this._hashUpdate === undefined) { - this._hashUpdate = JSON.stringify(this.publicPath); + override updateHash(hash: Hash): void { + if (this._hashUpdate === undefined) { + this._hashUpdate = JSON.stringify(this.publicPath); + } + hash.update(this._hashUpdate); } - hash.update(this._hashUpdate); - } - override serialize(context: ObjectSerializerContext): void { - const { write } = context; - write(this.publicPath); - super.serialize(context as any); - } + override serialize(context: ObjectSerializerContext): void { + const { write } = context; + write(this.publicPath); + super.serialize(context as any); + } - override deserialize(context: ObjectDeserializerContext): void { - const { read } = context; - this.publicPath = read(); - super.deserialize(context as any); + override deserialize(context: ObjectDeserializerContext): void { + const { read } = context; + this.publicPath = read(); + super.deserialize(context as any); + } } -} -AlphaTabWorkletDependency.Template = class AlphaTabWorkletDependencyTemplate extends webpack.dependencies.ModuleDependency.Template { - override apply(dependency: webpack.Dependency, source: webpack.sources.ReplaceSource, templateContext: { chunkGraph: webpack.ChunkGraph, moduleGraph: webpack.ModuleGraph, runtimeRequirements: Set }): void { - const { chunkGraph, moduleGraph, runtimeRequirements } = templateContext; - const dep = dependency as AlphaTabWorkletDependency; - - const block = moduleGraph.getParentBlock(dependency) as webpack.AsyncDependenciesBlock; - const entrypoint = chunkGraph.getBlockChunkGroup(block) as any; - - const workletImportBaseUrl = dep.publicPath - ? JSON.stringify(dep.publicPath) - : webpack.RuntimeGlobals.publicPath; - - const chunk = entrypoint.getEntrypointChunk() as webpack.Chunk; - - // worklet global scope has no 'self', need to inject it for compatibility with chunks - // some plugins like the auto public path need to right location. we pass this on from the main runtime - // some plugins rely on importScripts to be defined. - const workletInlineBootstrap = ` - globalThis.self = globalThis.self || globalThis; - globalThis.location = \${JSON.stringify(${webpack.RuntimeGlobals.baseURI})}; - globalThis.importScripts = (url) => { throw new Error("importScripts not available, dynamic loading of chunks not supported in this context", url) }; - `; - - chunkGraph.addChunkRuntimeRequirements(chunk, new Set([ - webpack.RuntimeGlobals.moduleFactories, - AlphaTabWorkerRuntimeModule.Key - ])) - runtimeRequirements.add(AlphaTabWorkletStartRuntimeModule.RuntimeGlobalWorkletGetStartupChunks); - - source.replace( - dep.range[0], - dep.range[1] - 1, - webpack.Template.asString([ - "(/* worklet bootstrap */ async function(__webpack_worklet__) {", - webpack.Template.indent([ - `await __webpack_worklet__.addModule(URL.createObjectURL(new Blob([\`${workletInlineBootstrap}\`], { type: "application/javascript; charset=utf-8" })));`, - `for (const fileName of ${AlphaTabWorkletStartRuntimeModule.RuntimeGlobalWorkletGetStartupChunks}(${chunk.id})) {`, - webpack.Template.indent([ - `await __webpack_worklet__.addModule(new URL(${workletImportBaseUrl} + fileName, ${webpack.RuntimeGlobals.baseURI}));` + AlphaTabWorkletDependency.Template = class AlphaTabWorkletDependencyTemplate extends ( + webPackWithAlphaTab.webpack.dependencies.ModuleDependency.Template + ) { + override apply( + dependency: webpackTypes.Dependency, + source: webpackTypes.sources.ReplaceSource, + templateContext: { + chunkGraph: webpackTypes.ChunkGraph; + moduleGraph: webpackTypes.ModuleGraph; + runtimeRequirements: Set; + } + ): void { + const { chunkGraph, moduleGraph, runtimeRequirements } = templateContext; + const dep = dependency as AlphaTabWorkletDependency; + + const block = moduleGraph.getParentBlock(dependency) as webpackTypes.AsyncDependenciesBlock; + const entrypoint = chunkGraph.getBlockChunkGroup(block) as any; + + const workletImportBaseUrl = dep.publicPath + ? JSON.stringify(dep.publicPath) + : webPackWithAlphaTab.webpack.RuntimeGlobals.publicPath; + + const chunk = entrypoint.getEntrypointChunk() as webpackTypes.Chunk; + + // worklet global scope has no 'self', need to inject it for compatibility with chunks + // some plugins like the auto public path need to right location. we pass this on from the main runtime + // some plugins rely on importScripts to be defined. + const workletInlineBootstrap = ` + globalThis.self = globalThis.self || globalThis; + globalThis.location = \${JSON.stringify(${webPackWithAlphaTab.webpack.RuntimeGlobals.baseURI})}; + globalThis.importScripts = (url) => { throw new Error("importScripts not available, dynamic loading of chunks not supported in this context", url) }; + `; + + chunkGraph.addChunkRuntimeRequirements( + chunk, + new Set([webPackWithAlphaTab.webpack.RuntimeGlobals.moduleFactories, webPackWithAlphaTab.alphaTab.WebWorkerRuntimeModuleKey]) + ); + + runtimeRequirements.add(webPackWithAlphaTab.alphaTab.RuntimeGlobalWorkletGetStartupChunks); + + source.replace( + dep.range[0], + dep.range[1] - 1, + webPackWithAlphaTab.webpack.Template.asString([ + '(/* worklet bootstrap */ async function(__webpack_worklet__) {', + webPackWithAlphaTab.webpack.Template.indent([ + `await __webpack_worklet__.addModule(URL.createObjectURL(new Blob([\`${workletInlineBootstrap}\`], { type: "application/javascript; charset=utf-8" })));`, + `for (const fileName of ${webPackWithAlphaTab.alphaTab.RuntimeGlobalWorkletGetStartupChunks}(${JSON.stringify(chunk.id)})) {`, + webPackWithAlphaTab.webpack.Template.indent([ + `await __webpack_worklet__.addModule(new URL(${workletImportBaseUrl} + fileName, ${webPackWithAlphaTab.webpack.RuntimeGlobals.baseURI}));` + ]), + '}' ]), - "}" - ]), - `})(alphaTabWorklet)` - ]) - ); - } + `})(alphaTabWorklet)` + ]) + ); + } + }; + + makeDependencySerializable(webPackWithAlphaTab, AlphaTabWorkletDependency, 'AlphaTabWorkletDependency'); + + webPackWithAlphaTab.alphaTab.registerWorkletDependency = ( + compilation: webpackTypes.Compilation, + normalModuleFactory: NormalModuleFactory + ) => { + compilation.dependencyFactories.set(AlphaTabWorkletDependency, normalModuleFactory); + compilation.dependencyTemplates.set(AlphaTabWorkletDependency, new AlphaTabWorkletDependency.Template()); + }; + + + webPackWithAlphaTab.alphaTab.createWorkletDependency = ( + request: string, + range: [number, number], + publicPath: string | undefined + ) => new AlphaTabWorkletDependency(request, range, publicPath); } - -makeDependencySerializable( - AlphaTabWorkletDependency, - 'AlphaTabWorkletDependency' -); diff --git a/src/webpack/AlphaTabWorkletStartRuntimeModule.ts b/src/webpack/AlphaTabWorkletStartRuntimeModule.ts index 10a1fbd59..7a7d87efd 100644 --- a/src/webpack/AlphaTabWorkletStartRuntimeModule.ts +++ b/src/webpack/AlphaTabWorkletStartRuntimeModule.ts @@ -1,57 +1,63 @@ /**@target web */ -import webpack from 'webpack' -import { AlphaTabWorkerRuntimeModule } from './AlphaTabWorkerRuntimeModule'; +import { isWorkerRuntime, webPackWithAlphaTab, webpackTypes } from './Utils'; -export class AlphaTabWorkletStartRuntimeModule extends webpack.RuntimeModule { - static readonly RuntimeGlobalWorkletGetStartupChunks = "__webpack_require__.wsc"; +export function injectWorkletRuntimeModule(webPackWithAlphaTab: webPackWithAlphaTab) { + class AlphaTabWorkletStartRuntimeModule extends webPackWithAlphaTab.webpack.RuntimeModule { + static readonly RuntimeGlobalWorkletGetStartupChunks = '__webpack_require__.wsc'; - constructor() { - super("alphaTab audio worklet chunk lookup", webpack.RuntimeModule.STAGE_BASIC); - } + constructor() { + super('alphaTab audio worklet chunk lookup', webPackWithAlphaTab.webpack.RuntimeModule.STAGE_BASIC); + } - override generate(): string | null { - const compilation = this.compilation!; - const workletChunkLookup = new Map(); - const chunkGraph = this.chunkGraph!; - - const allChunks = compilation.chunks; - for (const chunk of allChunks) { - const isWorkletEntry = chunkGraph - .getTreeRuntimeRequirements(chunk) - .has(AlphaTabWorkerRuntimeModule.Key); - - if (isWorkletEntry) { - - const workletChunks = Array.from(chunk.getAllReferencedChunks()).map(c => { - // force content chunk to be created - compilation.hooks.contentHash.call(c); - return compilation.getPath( - webpack.javascript.JavascriptModulesPlugin.getChunkFilenameTemplate( - c, - compilation.outputOptions - ), - { - chunk: c, - contentHashType: "javascript" - } - ) - }); - workletChunkLookup.set(String(chunk.id), workletChunks); + override generate(): string | null { + const compilation = this.compilation!; + const workletChunkLookup = new Map(); + + const allChunks = compilation.chunks; + for (const chunk of allChunks) { + const isWorkletEntry = isWorkerRuntime(chunk.runtime); + + if (isWorkletEntry) { + const workletChunks = Array.from(chunk.getAllReferencedChunks()).map(c => { + // force content chunk to be created + compilation.hooks.contentHash.call(c); + return compilation.getPath( + webPackWithAlphaTab.webpack.javascript.JavascriptModulesPlugin.getChunkFilenameTemplate( + c, + compilation.outputOptions + ), + { + chunk: c, + contentHashType: 'javascript' + } + ); + }); + workletChunkLookup.set(String(chunk.id), workletChunks); + } } - } - return webpack.Template.asString([ - `${AlphaTabWorkletStartRuntimeModule.RuntimeGlobalWorkletGetStartupChunks} = (() => {`, - webpack.Template.indent([ - "const lookup = new Map(", - webpack.Template.indent( - JSON.stringify(Array.from(workletChunkLookup.entries())) - ), - ");", - - "return (chunkId) => lookup.get(String(chunkId)) ?? [];" - ]), - "})();" - ]) + return webPackWithAlphaTab.webpack.Template.asString([ + `${AlphaTabWorkletStartRuntimeModule.RuntimeGlobalWorkletGetStartupChunks} = (() => {`, + webPackWithAlphaTab.webpack.Template.indent([ + 'const lookup = new Map(', + webPackWithAlphaTab.webpack.Template.indent(JSON.stringify(Array.from(workletChunkLookup.entries()))), + ');', + + 'return (chunkId) => lookup.get(String(chunkId)) ?? [];' + ]), + '})();' + ]); + } } -} \ No newline at end of file + + webPackWithAlphaTab.alphaTab.RuntimeGlobalWorkletGetStartupChunks = + AlphaTabWorkletStartRuntimeModule.RuntimeGlobalWorkletGetStartupChunks; + + webPackWithAlphaTab.alphaTab.registerWorkletRuntimeModule = (pluginName: string, compilation: webpackTypes.Compilation) => { + compilation.hooks.runtimeRequirementInTree + .for(AlphaTabWorkletStartRuntimeModule.RuntimeGlobalWorkletGetStartupChunks) + .tap(pluginName, (chunk: webpackTypes.Chunk) => { + compilation.addRuntimeModule(chunk, new AlphaTabWorkletStartRuntimeModule()); + }); + }; +} diff --git a/src/webpack/Utils.ts b/src/webpack/Utils.ts index ffc1b243e..fbd19c820 100644 --- a/src/webpack/Utils.ts +++ b/src/webpack/Utils.ts @@ -1,20 +1,48 @@ /**@target web */ - -import { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_ESM -} - // @ts-expect-error - from "webpack/lib/ModuleTypeConstants"; - -import webpack from 'webpack' - -// @ts-expect-error -import makeSerializable from 'webpack/lib/util/makeSerializable' - -export type NormalModuleFactory = webpack.Compilation['params']['normalModuleFactory']; +import type * as webpackTypes from 'webpack'; + +export { webpackTypes }; + +export type webPackWithAlphaTab = { + webpack: webpackTypes.Compiler['webpack']; + alphaTab: { + // web worker + registerWebWorkerRuntimeModule(pluginName: string, compilation: webpackTypes.Compilation): void; + WebWorkerRuntimeModuleKey: string; + + createWebWorkerDependency( + request: string, + range: [number, number], + publicPath: string | undefined + ): webpackTypes.dependencies.ModuleDependency; + registerWebWorkerDependency( + compilation: webpackTypes.Compilation, + normalModuleFactory: NormalModuleFactory + ): void; + + // audio worklet + registerWorkletRuntimeModule(pluginName: string, compilation: webpackTypes.Compilation): void; + RuntimeGlobalWorkletGetStartupChunks: string; + + createWorkletDependency( + request: string, + range: [number, number], + publicPath: string | undefined + ): webpackTypes.dependencies.ModuleDependency; + + registerWorkletDependency( + compilation: webpackTypes.Compilation, + normalModuleFactory: NormalModuleFactory + ): void; + }; +}; + +export type NormalModuleFactory = webpackTypes.Compilation['params']['normalModuleFactory']; export type Parser = any; -import type { Expression, NewExpression } from 'estree' +import type { Expression, NewExpression } from 'estree'; + +const JAVASCRIPT_MODULE_TYPE_AUTO = 'javascript/auto'; +const JAVASCRIPT_MODULE_TYPE_ESM = 'javascript/esm'; export interface Hash { update(data: string | Buffer, inputEncoding?: string): Hash; @@ -28,21 +56,33 @@ export interface ObjectDeserializerContext { read: () => any; } -export function makeDependencySerializable(dependency: any, key: string) { - makeSerializable(dependency, key); +export function makeDependencySerializable(webPackWithAlphaTab: webPackWithAlphaTab, dependency: any, key: string) { + webPackWithAlphaTab.webpack.util.serialization.register(dependency, key, null, { + serialize(obj, context) { + obj.serialize(context); + }, + deserialize(context) { + if (typeof dependency.deserialize === 'function') { + return dependency.deserialize(context); + } + const obj = new dependency(); + obj.deserialize(context); + return obj; + } + }); } -export function tapJavaScript(normalModuleFactory: NormalModuleFactory, pluginName: string, parserPlugin: (parser: any) => void) { - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(pluginName, parserPlugin); - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_ESM) - .tap(pluginName, parserPlugin); +export function tapJavaScript( + normalModuleFactory: NormalModuleFactory, + pluginName: string, + parserPlugin: (parser: any) => void +) { + normalModuleFactory.hooks.parser.for(JAVASCRIPT_MODULE_TYPE_AUTO).tap(pluginName, parserPlugin); + normalModuleFactory.hooks.parser.for(JAVASCRIPT_MODULE_TYPE_ESM).tap(pluginName, parserPlugin); } export function parseModuleUrl(parser: any, expr: Expression) { - if (expr.type !== "NewExpression" || (expr as NewExpression).arguments.length !== 2) { + if (expr.type !== 'NewExpression' || (expr as NewExpression).arguments.length !== 2) { return; } @@ -50,39 +90,36 @@ export function parseModuleUrl(parser: any, expr: Expression) { const [arg1, arg2] = newExpr.arguments; const callee = parser.evaluateExpression(newExpr.callee); - if (!callee.isIdentifier() || callee.identifier !== "URL") { + if (!callee.isIdentifier() || callee.identifier !== 'URL') { return; } const arg1Value = parser.evaluateExpression(arg1); - return [ - arg1Value, - [ - (arg1.range!)[0], - (arg2.range!)[1] - ] - ]; + return [arg1Value, [arg1.range![0], arg2.range![1]]]; } +const ALPHATAB_WORKER_RUNTIME_PREFIX = 'atworker_'; + export function getWorkerRuntime( parser: any, - compilation: webpack.Compilation, + compilation: webpackTypes.Compilation, cachedContextify: (s: string) => string, - workerIndexMap: WeakMap): string { - + workerIndexMap: WeakMap +): string { let i = workerIndexMap.get(parser.state) || 0; workerIndexMap.set(parser.state, i + 1); - let name = `${cachedContextify( - parser.state.module.identifier() - )}|${i}`; - const hash = webpack.util.createHash(compilation.outputOptions.hashFunction); + let name = `${cachedContextify(parser.state.module.identifier())}|${i}`; + const hash = compilation.compiler.webpack.util.createHash(compilation.outputOptions.hashFunction); hash.update(name); const digest = hash.digest(compilation.outputOptions.hashDigest) as string; - const runtime = digest.slice( - 0, - compilation.outputOptions.hashDigestLength - ); + const runtime = digest.slice(0, compilation.outputOptions.hashDigestLength); + return ALPHATAB_WORKER_RUNTIME_PREFIX + runtime; +} - return runtime; -}; \ No newline at end of file +export function isWorkerRuntime(runtime: webpackTypes.Chunk['runtime']) { + if (typeof runtime !== 'string') { + return false; + } + return runtime.startsWith(ALPHATAB_WORKER_RUNTIME_PREFIX); +} From f5eb128c381c3dc45afdfabf69523d5b2c1a5efb Mon Sep 17 00:00:00 2001 From: Daniel Kuschny Date: Sat, 15 Jun 2024 17:49:44 +0200 Subject: [PATCH 2/2] fix: Source Maps should not be shipped in NPM package (#1540) --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index b3a252c97..a20268d17 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,9 @@ "webpack-cli": "^5.1.4" }, "files": [ - "/dist/alphaTab*", + "/dist/alphaTab*.js", + "/dist/alphaTab*.mjs", + "/dist/alphaTab*.ts", "/dist/font/Bravura.*", "/dist/font/Bravura*.txt", "/dist/font/*.txt",