You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Next.js中从外部模块加载Worker脚本(无需手动复制到public)

Next.js中libarchive.js相关Webpack配置问题及解决方案

我在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 13:44:53