How to copy static files to build directory with Webpack?
Asked Answered
K

13

435

I'm trying to move from Gulp to Webpack. In Gulp I have task which copies all files and folders from /static/ folder to /build/ folder. How to do the same with Webpack? Do I need some plugin?

Kirsten answered 24/12, 2014 at 15:20 Comment(3)
Gulp is great to understand. just call webpack from gulpfile.js if you wantAmalekite
If you're using Laravel Mix, laravel.com/docs/5.8/mix#copying-files-and-directories is available.Anguish
Many of these answers are now out of date. file-loader is now deprecated as of webpack 5. The recommended way is to use asset-modules now, as per this answerHospital
V
721

Requiring assets using the file-loader module is the way webpack is intended to be used (source). However, if you need greater flexibility or want a cleaner interface, you can also copy static files directly using my copy-webpack-plugin (npm, Github). For your static to build example:

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

Compatibility note: If you're using an old version of webpack like [email protected], use [email protected]. Otherwise use latest.

Vitals answered 27/10, 2015 at 17:34 Comment(21)
This is so much simpler when you want to copy an entire directory (ie. static html, and other boilerplate images)!Nitrobenzene
Did the trick, thank you :) gave up on file loader after several failed attempts to get it to do a very simple command. your plugin worked first time.Oubre
This is almost what I want. Any way to strip off the paths / flatten? (that is, while using a wildcard / globbing)Aldredge
you can assign a 'to' field, unless you meant flatten the subfolders inside static, which I don't think is currently possible eg. ``` plugins: [ new CopyWebpackPlugin([ { from: 'static/things', to: 'static' } ]) ] ```Mar
@iX3, it's possible to flatten the paths (with globs) starting in v2.1.0Vitals
Require node.js version >= 0.12Hargis
This plugin does not re-copies files when they change. I.e. no watch functionality. Answer with file-loader provides that out of the box.Diazo
@Diazo The plugin does re-copy files if they change (dev-server or webpack --watch). If it's not copying for you, please file an issue.Vitals
How can you specify something like 'copy only the .html files'?Intricacy
@Vitals Simple Readme/Documentation just like this answer will be very much helpfulAholla
I am new to webpack, but I am having a hard time understanding why we need to use file-loader/url-loader/img-loader ... instead of just copying them? What is the benefit that we gain from doing this with, say, file-loader?Sunroom
It is possible to chain this with a loader someway? Let's say I want to copy a directory of svg images and I would want to optimize them when moving them with some kind of image loader?Ney
Since you are the plugin author. There is no better pace to ask this question. Using "copy-webpack-plugin" plugin... can I filter the files from source directory so that it only copies the file with certain file extencion ex. copy only ".html" ? RegardsAltissimo
@kevlened: you might update your answer to include write-file-webpack-plugin (required since v3 for writing to filesytem when webpack-dev-server is running)Weatherboarding
@Erowlin - Node v0.10 was still supported when I originally answered this question. Node v0.10 didn't support const, so var was used for compatibility. In this case, either one works, but I'll update it to make it consistent with modern examples.Vitals
Conflict with clean-webpack-plugin, how can I make copy-webpack-plugin working at the end of the building?Hilarius
copy-webpack-plugin does not preserve timestamps so deployment scripts can not tell what has changed.Stomatitis
thank you for actually answering the actual question posed in the title.Castile
thats an extremely bad solution. the recommended loaders will take care of compression and bundling a-lot of them together so there will be less http calls to files with less size.Complicate
You sir, saved my life. I explicitely needed this for a config solution related with docker and different production environments. Cheers!Nuncle
This is no longer the correct usage as CopyWebpackPlugin now takes an object rather than an array as argument: new CopyWebpackPlugin({ patterns: [{ from: 'static' }] })Atombomb
A
197

You don't need to copy things around, webpack works different than gulp. Webpack is a module bundler and everything you reference in your files will be included. You just need to specify a loader for that.

So if you write:

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

Webpack will first try to parse the referenced file as JavaScript (because that's the default). Of course, that will fail. That's why you need to specify a loader for that file type. The file- or url-loader for instance take the referenced file, put it into webpack's output folder (which should be build in your case) and return the hashed url for that file.

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

Usually loaders are applied via the webpack config:

// webpack.config.js

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

Of course you need to install the file-loader first to make this work.

Aric answered 26/12, 2014 at 0:7 Comment(14)
"Of course you need to install the file-loader first to make this work." Link to the aforementioned "file-loader" here. And here is how to install and use it.Stodge
Here's you can install file-loader npm install file-loader --save-devAvisavitaminosis
You still have the problem of HTML files and all references in them not being loaded.Feeder
Can I use those images in my css? I tried with url() in css but it didn't work. Should I change the routes?Rebozo
Do you bundle your css with webpack + css-loader + file-loader? Then url() references should work.Aric
yeah, if you want to get in the hell of webpack plugins, you can use file-loader, css-loader, style-loader, url-loader, ... and then you can have a great time configuring it the way you need and googling and not-sleeping :) or you can use copy-webpack-plugin and get your job done...Nipping
@KamilTomšík So your recommendation is that we should use a webpack plugin to avoid webpack plugins? (Just kidding. I got your point.)Timecard
The problem is that doesn't work all the time and I'm not even sure it answers the question depending on how you think about "static" files. Everyone always forgets about files not referenced by your code. What about, for example, JSON files you load asynchronously? Those won't be seen with an import/require. Or anything dynamically used where a specific file path was not imported? There's a copy webpack plugin, but it would be nice to see something directly handled by webpack.Bouquet
Your test would be better written.. /\.(jpe?g|gif|png|svg|woff|ttf|wav|mp3)$/Judicative
Ok, most part of all images are in css and html. So should i require all these images in my JS files using require('img.png'); to make it work with that file-loader? That's quite crazy thing.Mcknight
Just require your html with the html-loader and it will try to load all dependencies with the file-loader.Aric
i mean, yeah, this works great if you don't actually need to copy files... but... when you do actually need to copy static files into your build folder...Prepuce
If you don't want to handle some assets with webpack, you can use the copy-webpack-plugin to copy assets into your public folder—or just a regular copy command outside of webpack at all.Aric
File-loader is now deprecated and has been replaced with asset modulesHospital
W
62

If you want to copy your static files you can use the file-loader in this way :

for html files :

in webpack.config.js :

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

in your js file :

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

./static/ is relative to where your js file is.

You can do the same with images or whatever. The context is a powerful method to explore !!

Wallboard answered 3/2, 2016 at 12:44 Comment(7)
I prefer this method over the copy-webpack-plugin module. Additionally, I was able to get it working without using "&context=./app/static" in my webpack config. I only needed the require.context line.Carilyn
I'm trying this, it seems great but for one little problem I'm getting, which is that it is putting my index.html into a subdirectory it is creating called _ (underscore), what's going on ?Passbook
When you say "in your js file" what do you mean? What if I don't have a JS file?Scarper
absolutely. This one line in the entry script, i.e. main.js is importing everything within the static folder: require.context("./static/", true, /^.*/);Blackfish
This is a neat hack but if you're copying too many files over you'll run out of memory.Bouquet
When adding this to main.js - Property 'context' does not exist on type 'NodeRequire'.Misesteem
Doesn't work anymore in Webpack 5 (Error: Compiling RuleSet failed: Query arguments on 'loader' has been removed in favor of the 'options' property).Milwaukee
M
38

One advantage that the aforementioned copy-webpack-plugin brings that hasn't been explained before is that all the other methods mentioned here still bundle the resources into your bundle files (and require you to "require" or "import" them somewhere). If I just want to move some images around or some template partials, I don't want to clutter up my javascript bundle file with useless references to them, I just want the files emitted in the right place. I haven't found any other way to do this in webpack. Admittedly it's not what webpack originally was designed for, but it's definitely a current use case. (@BreakDS I hope this answers your question - it's only a benefit if you want it)

Methyl answered 21/9, 2017 at 18:13 Comment(0)
B
21

Webpack 5 adds Asset Modules which are essentially replacements for common file loaders. I've copied a relevant portion of the documentation below:

  • asset/resource emits a separate file and exports the URL. Previously achievable by using file-loader.
  • asset/inline exports a data URI of the asset. Previously achievable by using url-loader.
  • asset/source exports the source code of the asset. Previously achievable by using raw-loader.
  • asset automatically chooses between exporting a data URI and emitting a separate file. Previously achievable by using url-loader with asset size limit.

To add one in you can make your config look like so:

// webpack.config.js

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

To control how the files get output, you can use templated paths.

In the config you can set the global template here:

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

To override for a specific set of assets, you can do this:

// 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]'
                }
            }
        ]
    }
};

The provided templating will result in filenames that look like build/images/img.151cfcfa1bd74779aadb.png. The hash can be useful for cache busting etc. You should modify to your needs.

Bratislava answered 13/4, 2021 at 4:3 Comment(6)
Also, although your advice is entirely consistent with the doco, no assets are being copied for me :-\Meris
@DavidBullock you may be a victim of tree shaking. If you're not importing the asset somewhere in a used file, e.g. import myPath from "image.png"; and then using it, then Webpack won't copy it. Alternatively you may be using the Webpack dev server options which include an in memory file system and won't write to the filesystem. If none of that answers your question I'd suggest opening a question as there's limited information I can enumerate here.Bratislava
Ah ha! An explicit import gets the job done. I won't be putting an import for each asset I want copied though! Since this is not fundamentally a dependencies/bundling/minification/transpiling step in the build, I'll do it outside of webpack, I think.Meris
But for interest's sake, can one exclude certain rules from Tree Shaking? It would make sense to do so for Asset Modules, right?Meris
@DavidBullock I'd go ahead and use copy-webpack-plugin if you merely want assets copied. However for a more complete solution I'd suggest integrating fully with Webpack if possible, e.g. CSS and HTML both have respective setups. You might also need to use imports with expressions. This way unused images can still be pruned but you don't have to manually make Webpack recognize the imports are being used.Bratislava
Thank you for this answer! Was hoping I'd find the Webpack 5 approach hereCesarean
E
7

Above suggestions are good. But to try to answer your question directly I'd suggest using cpy-cli in a script defined in your package.json.

This example expects node to somewhere on your path. Install cpy-cli as a development dependency:

npm install --save-dev cpy-cli

Then create a couple of nodejs files. One to do the copy and the other to display a checkmark and message.

copy.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;

Add the script in package.json. Assuming scripts are in <project-root>/scripts/

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

To run the sript:

npm run copy

Elicia answered 21/8, 2016 at 14:54 Comment(3)
OP wanted to accomplish the file moving inside webpack, not using npm scripts?Veilleux
Even when OP wanted to solve this inside webpack, it is possible he is running webpack through npm, so he could add it to his build script where webpack is runPhonology
This actually makes more sense. Webpack doesn't have this builtin most likely because it is not meant as a replacement for gulp/make/etc.Katt
G
7

The way I load static images and fonts:

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/'
          },
        }
    ]
}

Don't forget to install file-loader to have that working.

Galla answered 23/12, 2018 at 16:45 Comment(6)
How do you handle duplicate filenames? Or better yet, you know of any way to preserve original path in new output directory?Grammar
You shouldn't have duplicate filename with the same extension name in your project. What's point for keeping duplicates though if their content is identical ? If not, then name them differently according to their content. Though why would you use webpack if you want to keep your things in the original path? If you want only JS translation then Babel should be enough.Galla
If you're implementing component based development (one of the main principles of which is encapsulation and more specifically in this case information hiding), then none of what you mentioned is pertinent. i.e. When someone adds a new component to the program they shouldn't need to check if there's another image named logo.png nor should they have to create an obtuse and "hopefully" unique filename to avoid global collision. Same reason we use CSS Modules.Grammar
As to why I want images to maintain the original path and filename; debugging mostly, same reason you'd use sourcemaps, but also SEO. Regardless, the answer to my question was actually very simple...[path][name].[ext] and there's plenty of flexibility provided to modify this for specific environment or use case...file-loaderGrammar
That being said we did implement a variation of your example so thank you for providing!Grammar
Alright, then I misunderstood the point. So you wanted to prevent filename duplicates. I thought you were asking, how it is possible to keep duplicates.Galla
S
6

You can write bash in your package.json:

# package.json
{
  "name": ...,
  "version": ...,
  "scripts": {
    "build": "NODE_ENV=production npm run webpack && cp -v <this> <that> && echo ok",
    ...
  }
}
Sowers answered 18/4, 2017 at 18:49 Comment(7)
In Windows, just use xcopy instead of cp: "build": "webpack && xcopy images dist\\images\\ /S /Y && xcopy css dist\\css\\ /S /Y"Bagging
Right, so your solution is to have a different script for each OS?Heathendom
Yes, for me a script for each OS is acceptable (it's really unix/non-unix, since a script on linux will run on Darwin or another POSIX *nix)Sowers
And that Windows example won't work with PowerShell as the default shell either.Unprintable
Unlike CopyWebpackPlugin, this option keeps file dates. OS issue might be problematic for open source, but for smaller teams is easily managed with Windows bash or wrapping xcopy with cp.bat.Stomatitis
Wrapping xcopy with cp.bat as per @AlienTechnology seems to be a reasonable solution, for cross-OS compatibility.Sowers
This is a webpack question... I can write bash to do most things... doesn't mean I shouldHospital
Y
5

Most likely you should use CopyWebpackPlugin which was mentioned in kevlened answer. Alternativly for some kind of files like .html or .json you can also use raw-loader or json-loader. Install it via npm install -D raw-loader and then what you only need to do is to add another loader to our webpack.config.js file.

Like:

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

Note: Restart the webpack-dev-server for any config changes to take effect.

And now you can require html files using relative paths, this makes it much easier to move folders around.

template: require('./nav.html')  
Yardley answered 8/8, 2016 at 12:28 Comment(0)
S
2

I was stuck here too. copy-webpack-plugin worked for me.

However, 'copy-webpack-plugin' was not necessary in my case (i learned later).

webpack ignores root paths
example

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

Hence, to make this work without using 'copy-webpack-plugin' use '~' in paths

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

'~' tells webpack to consider 'images' as a module

note: you might have to add the parent directory of images directory in

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

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

Sofko answered 26/4, 2017 at 8:22 Comment(1)
The ~ operator you mention, is specific to Vue JS and not a feature of webpack. Tried it with Svelte, doesn't seem to do the trick.Rattoon
J
2

The webpack config file (in webpack 2) allows you to export a promise chain, so long as the last step returns a webpack config object. See promise configuration docs. From there:

webpack now supports returning a Promise from the configuration file. This allows to do async processing in you configuration file.

You could create a simple recursive copy function that copies your file, and only after that triggers webpack. E.g.:

module.exports = function(){
    return copyTheFiles( inpath, outpath).then( result => {
        return { entry: "..." } // Etc etc
    } )
}
Joses answered 12/5, 2017 at 11:37 Comment(0)
J
1

lets say all your static assets are in a folder "static" at the root level and you want copy them to the build folder maintaining the structure of subfolder, then in your entry file) just put

//index.js or index.jsx

require.context("!!file?name=[path][name].[ext]&context=./static!../static/", true, /^\.\/.*\.*/);
Jolly answered 15/6, 2017 at 20:27 Comment(0)
N
0

In my case I used webpack for a wordpress plugin to compress js files, where the plugin files are already compressed and need to skip from the process.

optimization: {
    minimize: false,
},
externals: {
    "jquery": "jQuery",
},
entry: glob.sync('./js/plugin/**.js').reduce(function (obj, el) {
    obj[path.parse(el).name] = el;
    return obj
}, {}),
output: {
    path: path.resolve(__dirname, './js/dist/plugin'),
    filename: "[name].js",
    clean: true,
},

That used to copy the js file as it is to the build folder. Using any other methods like file-loader and copy-webpack create issues with that.

Hope it will help someone.

Nisen answered 16/9, 2022 at 11:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.