如何在Next.js中从外部模块加载Worker脚本(无需手动复制到public)
我在next.config.js中配置了CopyPlugin和针对libarchive.js路径的file loader,配置代码如下:
/** @type {import('next').NextConfig} */ import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin' import CopyPlugin from 'copy-webpack-plugin' import createMDXPlugin from '@next/mdx' import withYaml from 'next-plugin-yaml' import path from 'path' import { fileURLToPath } from 'url' import remarkGfm from 'remark-gfm' import remarkPrism from 'remark-prism' import rehypeSlug from 'rehype-slug' import remarkMath from 'remark-math' import rehypeKatex from 'rehype-katex' const __dirname = path.dirname(fileURLToPath(import.meta.url)) if ( process.env.LD_LIBRARY_PATH == null || !process.env.LD_LIBRARY_PATH.includes( `${process.env.PWD}/node_modules/canvas/build/Release:`, ) ) { process.env.LD_LIBRARY_PATH = `${ process.env.PWD }/node_modules/canvas/build/Release:${ process.env.LD_LIBRARY_PATH || '' }` } const mdxConfig = { extension: /\.mdx?$/, options: { providerImportSource: '@mdx-js/react', rehypePlugins: [[rehypeSlug], [rehypeKatex]], remarkPlugins: [[remarkPrism], [remarkMath], [remarkGfm]], }, } const withMDX = createMDXPlugin(mdxConfig) const nextConfig = withYaml( withMDX({ swcMinify: true, transpilePackages: [ '@termsurf/vine', '@textsurf/chat', 'react-syntax-highlighter', ], pageExtensions: ['mdx', 'md', 'tsx', 'ts'], poweredByHeader: false, trailingSlash: false, webpack: (config, options) => { config.experiments = { asyncWebAssembly: true, syncWebAssembly: true, layers: true, topLevelAwait: true, } config.resolve.fallback = { fs: false, path: false, dns: false, net: false, tls: false, } config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, }, extensions: ['.ts', '.tsx', '.js', '.json'], } config.plugins ??= [] if (!options.isServer) { config.plugins.push( new MonacoWebpackPlugin({ languages: [ 'json', 'markdown', 'css', 'typescript', 'javascript', 'html', 'graphql', 'python', 'scss', 'yaml', ], filename: 'static/[name].worker.js', }), ) } config.plugins.push( new CopyPlugin({ patterns: [ { from: path.resolve( path.join( __dirname, './node_modules/libarchive.js/dist', ), ), to: path.resolve( path.join(__dirname, './public/libarchive'), ), force: true, }, ], }), ) config.module.rules.push({ test: /\.wasm$/, type: 'asset/resource', }) // TODO: improve this function -> track https://github.com/vercel/next.js/issues/25852 if (options.isServer) { config.output.webassemblyModuleFilename = './../static/wasm/[modulehash].wasm' } else { config.output.webassemblyModuleFilename = 'static/wasm/[modulehash].wasm' } config.resolve.alias.canvas = false config.module.rules.push({ test: /\.js$/, include: [ path.resolve( path.join(__dirname, './node_modules/libarchive.js/dist'), ), ], loader: 'file', }) config.module.rules.push({ test: /\.(glsl|vs|fs|vert|frag)$/, type: 'asset/source', }) config.module.rules.push({ test: /\.(txt|node)$/, loader: 'raw-loader', }) return config }, }), ) export default nextConfig
但遇到三个问题:
- 开发服务器启动后,CopyPlugin并未将文件写入public目录;
- 浏览器访问
/worker-bundle.js时出现404错误; - file loader未生成对应的文件路径。
需求:无需手动复制文件到public目录,要么自动复制目标文件,要么能获取文件路径用new Worker(path)手动加载,目标文件是node_modules/libarchive.js/dist下的脚本。
问题1:CopyPlugin开发环境不复制文件
Next.js开发模式采用内存文件系统,不会直接写入public物理目录,且原配置的to路径指向public目录不符合Webpack输出逻辑。
解决方法:
调整CopyPlugin的输出路径为Webpack的输出目录(开发模式是.next/static,生产模式是out/static),无需写入public:
new CopyPlugin({ patterns: [ { from: path.join(__dirname, 'node_modules/libarchive.js/dist'), to: path.join(options.output.path, 'libarchive'), force: true, // 可选:排除worker脚本,避免和file loader重复处理 globOptions: { ignore: ['**/worker-bundle.js'], }, }, ], })
开发模式下文件会存入内存,访问路径为/libarchive/xxx.js;生产构建时会生成到out/static/libarchive目录。
问题2:worker-bundle.js 404错误
原配置未正确处理worker脚本的路径映射,Next.js中加载Worker不能直接使用相对路径,需通过Webpack获取正确的资源URL。
解决方法:
用Webpack 5的asset/resource类型(替代file loader)处理worker脚本,配置输出路径和public路径:
config.module.rules.push({ test: /worker-bundle\.js$/, include: path.join(__dirname, 'node_modules/libarchive.js/dist'), type: 'asset/resource', generator: { filename: 'libarchive/[name].[contenthash][ext]', publicPath: '/', }, })
在业务代码中导入worker脚本获取URL:
import workerUrl from 'libarchive.js/dist/worker-bundle.js' const worker = new Worker(workerUrl)
Webpack会自动处理文件输出路径,生成带哈希的URL避免缓存问题。
问题3:file loader未生成文件路径
原file loader规则太宽泛(匹配所有.js),被Next.js默认的JS处理规则覆盖,且未配置输出路径和publicPath,导致无法返回正确URL。
解决方法:
缩小规则匹配范围(只针对libarchive的worker脚本),使用Webpack 5原生的asset/resource类型替代file loader,配置如问题2中的规则即可。如果仍需使用file loader,可调整为:
config.module.rules.push({ test: /worker-bundle\.js$/, include: path.join(__dirname, 'node_modules/libarchive.js/dist'), loader: 'file-loader', options: { name: '[name].[contenthash].js', outputPath: 'libarchive', publicPath: '/libarchive', }, })
完整调整后的关键配置片段
整合上述修改后的Webpack配置部分:
webpack: (config, options) => { // ... 原有其他配置 // 调整CopyPlugin config.plugins.push( new CopyPlugin({ patterns: [ { from: path.join(__dirname, 'node_modules/libarchive.js/dist'), to: path.join(options.output.path, 'libarchive'), force: true, globOptions: { ignore: ['**/worker-bundle.js'], }, }, ], }) ) // 处理worker脚本 config.module.rules.push({ test: /worker-bundle\.js$/, include: path.join(__dirname, 'node_modules/libarchive.js/dist'), type: 'asset/resource', generator: { filename: 'libarchive/[name].[contenthash][ext]', publicPath: '/', }, }) // ... 原有其他规则 return config },
内容的提问来源于stack exchange,提问作者Lance Pollard

