【问题标题】:How to copy static files to build directory with Webpack?如何使用 Webpack 将静态文件复制到构建目录?
【发布时间】:2020-07-25 20:45:17
【问题描述】:

我正在尝试从 Gulp 移动到 Webpack。在Gulp 我的任务是将所有文件和文件夹从 /static/ 文件夹复制到 /build/ 文件夹。如何对Webpack 做同样的事情?我需要一些插件吗?

【问题讨论】:

标签: javascript webpack


【解决方案1】:

使用文件加载器模块要求资产是 webpack 的预期使用方式 (source)。但是,如果您需要更大的灵活性或想要更简洁的界面,您也可以使用我的copy-webpack-pluginnpmGithub)直接复制静态文件。对于您的 staticbuild 示例:

const CopyWebpackPlugin = require('copy-webpack-plugin');
 
module.exports = {
    context: path.join(__dirname, 'your-app'),
    plugins: [
        new CopyWebpackPlugin({
            patterns: [
                { from: 'static' }
            ]
        })
    ]
};

兼容性说明:如果您使用的是旧版本的 webpack,例如 webpack@4.x.x,请使用 copy-webpack-plugin@6.x.x。否则使用最新的。

【讨论】:

  • 当您想要复制整个目录(即静态 html 和其他样板图像)时,这要简单得多!
  • 成功了,谢谢 :) 在几次尝试让它执行一个非常简单的命令失败后,放弃了文件加载器。你的插件第一次工作。
  • @Yan 如果文件发生变化(dev-server 或 webpack --watch),插件会重新复制文件。如果它不是为您复制的,请提出问题。
  • 我是 webpack 的新手,但我很难理解为什么我们需要使用 file-loader/url-loader/img-loader ... 而不仅仅是复制它们?使用文件加载器,我们这样做有什么好处?
  • 既然你是插件作者。没有比这更好的速度来问这个问题了。使用“copy-webpack-plugin”插件...我可以从源目录中过滤文件,以便它只复制具有特定文件扩展名的文件。只复制“.html”?问候
【解决方案2】:

你不需要复制东西,webpack 的工作方式与 gulp 不同。 Webpack 是一个模块捆绑器,您在文件中引用的所有内容都将包含在内。你只需要为此指定一个加载器。

所以如果你写:

var myImage = require("./static/myImage.jpg");

Webpack 将首先尝试将引用的文件解析为 JavaScript(因为这是默认设置)。当然,那会失败。这就是您需要为该文件类型指定加载程序的原因。 file- 或 url-loader 例如获取引用的文件,将其放入 webpack 的输出文件夹(在您的情况下应该是 build)并返回该文件的哈希 URL。

var myImage = require("./static/myImage.jpg");
console.log(myImage); // '/build/12as7f9asfasgasg.jpg'

通常通过 webpack 配置应用加载器:

// webpack.config.js

module.exports = {
    ...
    module: {
        loaders: [
            { test: /\.(jpe?g|gif|png|svg|woff|ttf|wav|mp3)$/, loader: "file" }
        ]
    }
};

当然,您需要先安装文件加载器才能完成这项工作。

【讨论】:

  • "当然,您需要先安装文件加载器才能完成这项工作。" 链接到上述“文件加载器”here。而here是如何安装使用的。
  • 您仍然有 HTML 文件的问题,并且其中的所有引用都没有被加载。
  • 是的,如果你想进入 webpack 插件的地狱,你可以使用 file-loader, css-loader, style-loader, url-loader, ... 然后你可以有一个很高兴按照您需要的方式配置它并使用谷歌搜索和不睡觉:) 或者您可以使用 copy-webpack-plugin 并完成您的工作......
  • @KamilTomšík 所以你的建议是我们应该使用 webpack 插件来避免 webpack 插件? (开个玩笑。我明白你的意思。)
  • 好的,大部分图片都是css和html格式。那么我应该使用 require('img.png'); 在我的 JS 文件中要求所有这些图像吗?让它与那个文件加载器一起工作?这真是太疯狂了。
【解决方案3】:

如果你想复制你的静态文件,你可以通过这种方式使用文件加载器:

对于 html 文件:

在 webpack.config.js 中:

module.exports = {
    ...
    module: {
        loaders: [
            { test: /\.(html)$/,
              loader: "file?name=[path][name].[ext]&context=./app/static"
            }
        ]
    }
};

在你的 js 文件中:

  require.context("./static/", true, /^\.\/.*\.html/);

./static/ 是相对于你的 js 文件所在的位置。

您可以对图像或其他任何东西做同样的事情。 上下文是一种强大的探索方法!

【讨论】:

  • 我更喜欢这种方法而不是 copy-webpack-plugin 模块。此外,我能够在我的 webpack 配置中不使用“&context=./app/static”的情况下让它工作。我只需要 require.context 行。
  • 我正在尝试这个,它看起来很棒,但我遇到了一个小问题,那就是它将我的 index.html 放入它正在创建的名为 _ 的子目录中(下划线) ,怎么回事?
  • 当你说“在你的 js 文件中”是什么意思?如果我没有 JS 文件怎么办?
  • 绝对。入口脚本中的这一行,即main.js 正在导入static 文件夹中的所有内容:require.context("./static/", true, /^.*/);
  • 这是一个巧妙的技巧,但如果你复制太多文件,你会耗尽内存。
【解决方案4】:

前面提到的 copy-webpack-plugin 带来的一个之前没有解释过的优点是这里提到的所有其他方法仍然将资源捆绑到你的包文件中(并且要求你“需要”或在某处“导入”它们)。如果我只想移动一些图像或一些模板部分,我不想用对它们的无用引用来弄乱我的 javascript 包文件,我只希望在正确的位置发出文件。我还没有在 webpack 中找到任何其他方法来做到这一点。诚然,这不是 webpack 最初的设计目的,但它绝对是当前的用例。 (@BreakDS 我希望这能回答你的问题——如果你想要的话,这只是一个好处)

【讨论】:

    【解决方案5】:

    以上建议都不错。但要尝试直接回答您的问题,我建议在您的package.json 中定义的脚本中使用cpy-cli

    这个例子期望node 在你的路径上的某个地方。安装cpy-cli作为开发依赖:

    npm install --save-dev cpy-cli

    然后创建几个 nodejs 文件。一个用于复制,另一个用于显示复选标记和消息。

    复制.js

    #!/usr/bin/env node
    
    var shelljs = require('shelljs');
    var addCheckMark = require('./helpers/checkmark');
    var path = require('path');
    
    var cpy = path.join(__dirname, '../node_modules/cpy-cli/cli.js');
    
    shelljs.exec(cpy + ' /static/* /build/', addCheckMark.bind(null, callback));
    
    function callback() {
      process.stdout.write(' Copied /static/* to the /build/ directory\n\n');
    }
    

    checkmark.js

    var chalk = require('chalk');
    
    /**
     * Adds mark check symbol
     */
    function addCheckMark(callback) {
      process.stdout.write(chalk.green(' ✓'));
      callback();
    }
    
    module.exports = addCheckMark;
    

    package.json 中添加脚本。假设脚本在<project-root>/scripts/

    ...
    "scripts": {
      "copy": "node scripts/copy.js",
    ...
    

    运行脚本:

    npm run copy

    【讨论】:

    • OP 想要完成文件在 webpack 中的移动,而不是使用 npm 脚本?
    • 即使 OP 想在 webpack 中解决这个问题,他也有可能通过 npm 运行 webpack,因此他可以将其添加到运行 webpack 的构建脚本中
    • 这实际上更有意义。 Webpack 很可能没有这个内置函数,因为它不是 gulp/make/etc 的替代品。
    【解决方案6】:

    我加载静态imagesfonts的方式:

    module: {
        rules: [
          ....
    
          {
            test: /\.(jpe?g|png|gif|svg)$/i,
            /* Exclude fonts while working with images, e.g. .svg can be both image or font. */
            exclude: path.resolve(__dirname, '../src/assets/fonts'),
            use: [{
              loader: 'file-loader',
              options: {
                name: '[name].[ext]',
                outputPath: 'images/'
              }
            }]
          },
          {
            test: /\.(woff(2)?|ttf|eot|svg|otf)(\?v=\d+\.\d+\.\d+)?$/,
            /* Exclude images while working with fonts, e.g. .svg can be both image or font. */
            exclude: path.resolve(__dirname, '../src/assets/images'),
            use: [{
              loader: 'file-loader',
              options: {
                name: '[name].[ext]',
                outputPath: 'fonts/'
              },
            }
        ]
    }
    

    不要忘记安装 file-loader 以使其正常工作。

    【讨论】:

    • 如何处理重复的文件名?或者更好的是,您知道在新输出目录中保留原始路径的任何方法吗?
    • 您的项目中不应有具有相同扩展名的重复文件名。如果它们的内容相同,保持重复有什么意义?如果不是,则根据其内容对它们进行不同的命名。但是,如果您想将内容保留在原始路径中,为什么还要使用 webpack?如果你只想要 JS 翻译,那么 Babel 就足够了。
    • 如果你正在实现基于组件的开发(其中一个主要原则是封装,在这种情况下更具体地说是information hiding,那么你没有提到是相关的。即,当有人向程序添加新组件时,他们不需要检查是否有另一个名为 logo.png 的图像,也不需要创建一个钝且“希望”的唯一文件名以避免全局冲突。我们使用CSS Modules的原因相同。
    • 至于为什么我想让图片保持原来的路径和文件名;主要是调试,同样的原因你会使用源映射,还有SEO。无论如何,我的问题的答案实际上非常简单...[path][name].[ext] 并且提供了足够的灵活性来针对特定环境或用例进行修改...file-loader
    • 话虽如此,我们确实实现了您的示例的变体,所以感谢您提供!
    【解决方案7】:

    Webpack 5 添加了Asset Modules,它本质上是普通文件加载器的替代品。我复制了以下文档的相关部分:

    • asset/resource 发出一个单独的文件并导出 URL。以前可以通过使用file-loader 来实现。
    • asset/inline 导出资产的数据 URI。以前可以通过使用 url-loader 来实现。
    • asset/source 导出资产的源代码。以前可以通过使用 raw-loader 来实现。
    • asset 自动在导出数据 URI 和发出单独文件之间进行选择。以前可以通过使用带有资产大小限制的 url-loader 来实现。

    你可以让你的配置看起来像这样:

    // webpack.config.js
    
    module.exports = {
        ...
        module: {
            rules: [
                {
                    test: /\.(jpe?g|gif|png|svg|woff|ttf|wav|mp3)$/,
                    type: "asset/resource"
                }
            ]
        }
    };
    

    您可以使用templated paths 处理文件的复制方式。对于所有资产的默认模板,您可以执行以下操作:

    // webpack.config.js
    module.exports = {
        ...
        output: {
            ...
            assetModuleFilename: '[path][name].[hash][ext][query]'
        }
    }
    

    对于一组特定的资产,您需要这样做:

    // webpack.config.js
    
    module.exports = {
        ...
        module: {
            rules: [
                {
                    test: /\.(jpe?g|gif|png|svg|woff|ttf|wav|mp3)$/,
                    type: "asset/resource"
                    generator: {
                        filename: '[path][name].[hash][ext][query]'
                    }
                }
            ]
        }
    };
    

    提供的模板将生成类似于build/images/img.151cfcfa1bd74779aadb.png 的文件名。将模板修改为您的用例。

    【讨论】:

    • 另外,虽然您的建议与文档完全一致,但没有为我复制任何资产:-\
    • @DavidBullock 你可能是摇树的受害者。如果您没有在使用过的文件中的某处导入资产,例如import myPath from "image.png"; 然后使用它,那么 Webpack 不会复制它。或者,您可能正在使用包含内存文件系统并且不会写入文件系统的 Webpack 开发服务器选项。如果这些都不能回答你的问题,我建议你提出一个问题,因为我可以在这里列举的信息有限。
    • 啊哈!明确的import 可以完成工作。不过,我不会为我想要复制的每个资产添加import!由于这从根本上不是构建中的依赖项/捆绑/缩小/转译步骤,我认为我将在 webpack 之外进行。
    • 但是出于兴趣,可以从 Tree Shaking 中排除某些规则吗?对资产模块这样做是有意义的,对吧?
    • @DavidBullock 如果您只想复制资产,我会继续使用copy-webpack-plugin。但是,对于更完整的解决方案,我建议尽可能与 Webpack 完全集成,例如CSS 和 HTML 都有各自的设置。您可能还需要使用imports with expressions。这种方式仍然可以修剪未使用的图像,但您不必手动让 Webpack 识别正在使用的导入。
    【解决方案8】:

    您很可能应该使用 kevlened 答案中提到的 CopyWebpackPlugin。对于.html.json 之类的某些文件,您也可以使用 raw-loader 或 json-loader。通过npm install -D raw-loader 安装它,然后您只需在我们的webpack.config.js 文件中添加另一个加载程序。

    喜欢:

    {
        test: /\.html/,
        loader: 'raw'
    }
    

    注意:重新启动 webpack-dev-server 以使任何配置更改生效。

    现在您可以使用相对路径来请求 html 文件,这使得移动文件夹变得更加容易。

    template: require('./nav.html')  
    

    【讨论】:

      【解决方案9】:

      您可以在 package.json 中编写 bash:

      # package.json
      {
        "name": ...,
        "version": ...,
        "scripts": {
          "build": "NODE_ENV=production npm run webpack && cp -v <this> <that> && echo ok",
          ...
        }
      }
      

      【讨论】:

      • 在 Windows 中,只需使用 xcopy 而不是 cp:"build": "webpack &amp;&amp; xcopy images dist\\images\\ /S /Y &amp;&amp; xcopy css dist\\css\\ /S /Y"
      • 对,所以您的解决方案是为每个操作系统设置不同的脚本?
      • 是的,对我来说,每个操作系统的脚本都是可以接受的(实际上是 unix/非 unix,因为 linux 上的脚本将在 Darwin 或其他 POSIX *nix 上运行)
      • 而且该 Windows 示例也无法使用 PowerShell 作为默认 shell。
      • 与 CopyWebpackPlugin 不同,此选项保留文件日期。操作系统问题对于开源来说可能是个问题,但对于较小的团队来说,使用 Windows bash 或使用 cp.bat 包装 xcopy 可以轻松管理。
      【解决方案10】:

      我也被困在这里。 copy-webpack-plugin 对我有用。

      但是,在我的情况下,“copy-webpack-plugin”不是必需的(我后来才知道)。

      webpack 忽略根路径
      例子

      <img src="/images/logo.png'>
      

      因此,在不使用“copy-webpack-plugin”的情况下完成这项工作 在路径中使用 '~'

      <img src="~images/logo.png'>
      

      '~' 告诉 webpack 将 'images' 视为一个模块

      注意: 您可能需要在中添加图像目录的父目录

      resolve: {
          modules: [
              'parent-directory of images',
              'node_modules'
          ]
      }
      

      访问https://vuejs-templates.github.io/webpack/static.html

      【讨论】:

      • 您提到的~ 运算符是Vue JS特有的,不是webpack的功能。用 Svelte 尝试过,似乎没有成功。
      【解决方案11】:

      webpack 配置文件(在 webpack 2 中)允许你导出一个 Promise 链,只要最后一步返回一个 webpack 配置对象。 See promise configuration docs。从那里:

      webpack 现在支持从配置文件返回一个 Promise。这允许在您的配置文件中进行异步处理。

      你可以创建一个简单的递归复制函数来复制你的文件,然后才触发 webpack。例如:

      module.exports = function(){
          return copyTheFiles( inpath, outpath).then( result => {
              return { entry: "..." } // Etc etc
          } )
      }
      

      【讨论】:

        【解决方案12】:

        假设您的所有静态资产都在根级别的“静态”文件夹中,并且您希望将它们复制到构建文件夹中维护子文件夹的结构,然后 在您的条目文件中)只需放入

        //index.js or index.jsx
        
        require.context("!!file?name=[path][name].[ext]&context=./static!../static/", true, /^\.\/.*\.*/);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-09-14
          • 2016-07-14
          • 1970-01-01
          • 1970-01-01
          • 2020-12-17
          相关资源
          最近更新 更多