(Next JS 10.2) 音频工作集支持
(Next JS 10.2) Audio Worklet Support
我们的应用程序构建在 Next 之上,目前我们正在将我们的录音引擎从脚本处理器迁移到 Audio Worklet API。 (当然具有向后兼容性)过渡的一部分也是升级到 Webpack 5。我们正在使用 Web Workers 和 Audio Worklets。在切换到原生支持 Workers 的 Webpack 5 之前(或者看起来如此),我们使用 worker-plugin。它对两者都非常有效,但是随着过渡我们不能再依赖它,因为它使用过时的 webpack 的 APIs,并且 Next 附带了自己的捆绑 Webpack 4 和 5,它们似乎没有倒退包括兼容性。
现在的挑战是让这些与 Next 捆绑的 Webpack 5 一起工作。第一个问题来自 web worker 的全局范围未定义。这可以通过将 config.output.globalObject
设置为 "(typeof self !== 'undefined' ? self : this)"
来解决。一旦 worker 正常运行,下一个问题就是尝试捆绑 worklet 的代码。目前,Webpack 5 不支持 worklet 导入,但它们公开了解析器配置,可用于告诉 webpack 将其作为工作程序加载,如 Github issue 所示。除非您还设置 config.output.publicPath = "/_next/";
,否则 Next 将失败。通过 audioContext.audioWorklet.addModule(...)
加载块时,代码会崩溃并显示 Uncaught TypeError: Cannot read property 'webpackChunk_N_E' of undefined
。如果我们检查包含捆绑的 worklet 代码的块,我们会看到这个 prop 正在以下列方式使用:
var chunkLoadingGlobal = (typeof self !== 'undefined' ? self : this)["webpackChunk_N_E"] = (typeof self !== 'undefined' ? self : this)["webpackChunk_N_E"] || [];
var parentChunkLoadingFunction = chunkLoadingGlobal.push.bind(chunkLoadingGlobal);
.
.
.
(typeof self !== 'undefined' ? self : this)["webpackHotUpdate_N_E"] = function...
很明显,AudioWorkletGlobalScope
既没有 self
也没有 this
,所以这就是进展不太顺利的原因。
所以问题是,如何解决这个问题? Audio Worklets 似乎仍然很少受到 Next 和 Webpack 的关注,尽管它现在甚至在 Safari 中默认可用。 (自 14.5 起)
下面是代表我们当前状态的代码片段。 (我们使用的是 Typescript)
next.config.js
module.exports = withPlugins([...], {
future: {webpack5: true},
webpack: (config, options) => {
config.output.globalObject = `(typeof self !== 'undefined' ? self : this)`;
config.output.publicPath = "/_next/";
config.resolve = {
...config.resolve,
alias: {
...config.resolve.alias,
"audio-worklet": path.resolve(__dirname, "src/util/audio-worklet")
}
}
config.module.parser = {
...config.module.parser,
javascript: {
worker: ["AudioWorklet from audio-worklet", "..."]
}
}
config.output.chunkFilename = options.isServer
? `${options.dev ? "[name].[hash]" : "[name].[chunkhash]"}.js`
: `static/chunks/${options.dev ? "[name].[hash]" : "[name].[chunkhash]"}.js`;
return config;
}
});
src/util/audio-worklet.ts
export const AudioWorklet = (url: URL) => {
return url as unknown as string;
};
.../audio-context.ts
import { AudioWorklet } from "audio-worklet";
const audioContext = new AudioContext();
audioContext.audioWorklet.addModule(new AudioWorklet(new URL("path/to/processor.worklet.ts", import.meta.url)));
.../audio-worklet-processor.worklet.ts
// import statements for utility code
class Processor extends AudioWorkletProcessor {
// Your run of the mill processor code
}
registerProcessor("processor", Processor);
来源:
- https://github.com/GoogleChromeLabs/worker-plugin
- https://webpack.js.org/configuration/module/#moduleparserjavascript
- https://github.com/webpack/webpack/issues/11543
- https://github.com/vercel/next.js/tree/canary/examples/with-web-worker
更新
经过大量挖掘,这个问题似乎是由 webpack 在块中导入脚本引起的。具体来说,当我们尝试导入任何重要的代码时,例如 lodash,或使用 babel 认为需要使用其填充程序的数据结构,Webpack 会注入 installChunk
使用 importScripts
的函数,这显然是AudioWorklets 不支持。这还会插入上面导致 Uncaught TypeError: Cannot read property 'webpackChunk_N_E' of undefined
.
的代码
这是一个一对一复制它的 repo:https://github.com/thecynicalpaul/test-audio-worklet-webpack5(nmp i
,然后 npm run dev
)
我也为此苦苦挣扎,发现删除所有为 webpack 注入的客户端代码可以解决这个问题,没有明显的回归。这是一种 hack,但它有效:
{
test: /webpack-dev-server\client/,
loader: "null-loader"
}
来自
我自己回答这个问题,因为我们找到了一个临时解决方案。
问题的发生是由于 Webpack 5 中的 URL 加载器将工作集文件视为网络工作者文件。这意味着,context 是 worker 的,它会尝试使用 importScripts
等显然不受支持的 API。
我们得出的解决方案是禁用与工作集相关的任何块的块拆分,有效地消除了使用 importScripts
:
的需要
(在 next.config.js
的 webpack 函数中)
if (!options.isServer && !options.dev) {
config.optimization.splitChunks = {
chunks: (chunk) => {
// this may vary widely on your loader config
if (chunk.name && chunk.name.includes("worklet")) {
return false;
}
return true;
}
}
}
这不是宏伟计划中的最佳解决方案,原因很明显,webpack 的团队目前正在寻找更好的方法来处理这个问题。
我们的应用程序构建在 Next 之上,目前我们正在将我们的录音引擎从脚本处理器迁移到 Audio Worklet API。 (当然具有向后兼容性)过渡的一部分也是升级到 Webpack 5。我们正在使用 Web Workers 和 Audio Worklets。在切换到原生支持 Workers 的 Webpack 5 之前(或者看起来如此),我们使用 worker-plugin。它对两者都非常有效,但是随着过渡我们不能再依赖它,因为它使用过时的 webpack 的 APIs,并且 Next 附带了自己的捆绑 Webpack 4 和 5,它们似乎没有倒退包括兼容性。
现在的挑战是让这些与 Next 捆绑的 Webpack 5 一起工作。第一个问题来自 web worker 的全局范围未定义。这可以通过将 config.output.globalObject
设置为 "(typeof self !== 'undefined' ? self : this)"
来解决。一旦 worker 正常运行,下一个问题就是尝试捆绑 worklet 的代码。目前,Webpack 5 不支持 worklet 导入,但它们公开了解析器配置,可用于告诉 webpack 将其作为工作程序加载,如 Github issue 所示。除非您还设置 config.output.publicPath = "/_next/";
,否则 Next 将失败。通过 audioContext.audioWorklet.addModule(...)
加载块时,代码会崩溃并显示 Uncaught TypeError: Cannot read property 'webpackChunk_N_E' of undefined
。如果我们检查包含捆绑的 worklet 代码的块,我们会看到这个 prop 正在以下列方式使用:
var chunkLoadingGlobal = (typeof self !== 'undefined' ? self : this)["webpackChunk_N_E"] = (typeof self !== 'undefined' ? self : this)["webpackChunk_N_E"] || [];
var parentChunkLoadingFunction = chunkLoadingGlobal.push.bind(chunkLoadingGlobal);
.
.
.
(typeof self !== 'undefined' ? self : this)["webpackHotUpdate_N_E"] = function...
很明显,AudioWorkletGlobalScope
既没有 self
也没有 this
,所以这就是进展不太顺利的原因。
所以问题是,如何解决这个问题? Audio Worklets 似乎仍然很少受到 Next 和 Webpack 的关注,尽管它现在甚至在 Safari 中默认可用。 (自 14.5 起)
下面是代表我们当前状态的代码片段。 (我们使用的是 Typescript)
next.config.js
module.exports = withPlugins([...], {
future: {webpack5: true},
webpack: (config, options) => {
config.output.globalObject = `(typeof self !== 'undefined' ? self : this)`;
config.output.publicPath = "/_next/";
config.resolve = {
...config.resolve,
alias: {
...config.resolve.alias,
"audio-worklet": path.resolve(__dirname, "src/util/audio-worklet")
}
}
config.module.parser = {
...config.module.parser,
javascript: {
worker: ["AudioWorklet from audio-worklet", "..."]
}
}
config.output.chunkFilename = options.isServer
? `${options.dev ? "[name].[hash]" : "[name].[chunkhash]"}.js`
: `static/chunks/${options.dev ? "[name].[hash]" : "[name].[chunkhash]"}.js`;
return config;
}
});
src/util/audio-worklet.ts
export const AudioWorklet = (url: URL) => {
return url as unknown as string;
};
.../audio-context.ts
import { AudioWorklet } from "audio-worklet";
const audioContext = new AudioContext();
audioContext.audioWorklet.addModule(new AudioWorklet(new URL("path/to/processor.worklet.ts", import.meta.url)));
.../audio-worklet-processor.worklet.ts
// import statements for utility code
class Processor extends AudioWorkletProcessor {
// Your run of the mill processor code
}
registerProcessor("processor", Processor);
来源:
- https://github.com/GoogleChromeLabs/worker-plugin
- https://webpack.js.org/configuration/module/#moduleparserjavascript
- https://github.com/webpack/webpack/issues/11543
- https://github.com/vercel/next.js/tree/canary/examples/with-web-worker
更新
经过大量挖掘,这个问题似乎是由 webpack 在块中导入脚本引起的。具体来说,当我们尝试导入任何重要的代码时,例如 lodash,或使用 babel 认为需要使用其填充程序的数据结构,Webpack 会注入 installChunk
使用 importScripts
的函数,这显然是AudioWorklets 不支持。这还会插入上面导致 Uncaught TypeError: Cannot read property 'webpackChunk_N_E' of undefined
.
这是一个一对一复制它的 repo:https://github.com/thecynicalpaul/test-audio-worklet-webpack5(nmp i
,然后 npm run dev
)
我也为此苦苦挣扎,发现删除所有为 webpack 注入的客户端代码可以解决这个问题,没有明显的回归。这是一种 hack,但它有效:
{
test: /webpack-dev-server\client/,
loader: "null-loader"
}
来自
我自己回答这个问题,因为我们找到了一个临时解决方案。
问题的发生是由于 Webpack 5 中的 URL 加载器将工作集文件视为网络工作者文件。这意味着,context 是 worker 的,它会尝试使用 importScripts
等显然不受支持的 API。
我们得出的解决方案是禁用与工作集相关的任何块的块拆分,有效地消除了使用 importScripts
:
(在 next.config.js
的 webpack 函数中)
if (!options.isServer && !options.dev) {
config.optimization.splitChunks = {
chunks: (chunk) => {
// this may vary widely on your loader config
if (chunk.name && chunk.name.includes("worklet")) {
return false;
}
return true;
}
}
}
这不是宏伟计划中的最佳解决方案,原因很明显,webpack 的团队目前正在寻找更好的方法来处理这个问题。