Johnny Copperstone
Johnny Copperstone

Reputation: 793

Include assets from webpack bundled npm package

I've been banging my head over this for a while, and am wondering if it's even possible to begin with. Thanks for any help with this!

The npm package

I've got an npm package which is basically a library of React components. This library has embedded stylesheets, which references assets like fonts and images from the CSS. These are then all bundled using webpack into my-package.js.

The config for this looks like:

var path = require('path');

module.exports = {
  module: {
    loaders: [
      {
        test: /\.js$/,
        loaders: ['babel-loader'],
        exclude: /node_modules/
      },
      {
        test: /\.css$/,
        loader: "style-loader!css-loader"
      },
      {
        test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/,
        loader: 'file-loader'
      },
      {
        test: /\.styl$/,
        loader: 'style-loader!css-loader!stylus-loader'
      }
    ]
  },
  entry: [
    './lib/components/index.js',
    './lib/index.styl'
  ],
  output: {
    path: path.join(__dirname, 'build/'),
    filename: 'my-package.js'
  }
}

With ./lib/components/index.js looking like:

import '../index.styl';
import MyComponent from './my-component.js';

export {
  MyComponent
}

So far, so good.

The application

Now in another code base I've got the main application, which install this npm package.

My application root requires this package...

import MyPackage from 'my-package';

And is then itself webpack bundled and loaded onto the browser. All the scripts and style blocks are bundled correctly, however the styles which reference the assets are using the relative url from the npm package itself, therefore giving me 404s from the application.

console errs

Is there any way to tell webpack to resolve these images from node_modules/my-package/build/[webpack-generated-name].jpg ?

My application's webpack config looks like this:

var path = require('path'),
    webpack = require('webpack');

module.exports = {
  devtool: '#eval-source-map',
  entry: [
    'my-package',
    'webpack/hot/only-dev-server',
    './app/index.js',
  ],
  output: {
    path: path.join(__dirname, 'build/static'),
    filename: 'bundled.js',
    publicPath: '/',
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ],
  resolve: {
    extensions: ['', '.js']
  },
  resolveLoader: {
    'fallback': path.join(__dirname, 'node_modules')
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loaders: ['react-hot', 'babel'],
        exclude: /node_modules/,
        include: __dirname
      },
      {
        test: /\.css?$/,
        loader: "style-loader!css-loader",
        include: __dirname
      },
      {
        test: /\.(jpg|jpeg|ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
        loader: 'file-loader'
      },
      {
        test: /\.styl$/,
        loader: 'style-loader!css-loader!stylus-loader'
      }
    ]
  }
};

Upvotes: 29

Views: 13856

Answers (2)

Oguz Tecirlioglu
Oguz Tecirlioglu

Reputation: 53

When bundling your NPM package, you could inline all the static assets into your final bundle. That way, your index.js that Webpack bundles for you would have all the static assets it needs, and simply importing that wherever you need your React components would do the trick.

The best way to inline static assets as of Webpack 5 is by using the "asset/inline" module from Asset Modules, which you can read about here: https://webpack.js.org/guides/asset-modules/

Simply put, your webpack.config.js should have a section as such:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
 module: {
   rules: [
     {
       test: /\.(png|jpg|gif)$/i,
       type: 'asset/inline'
     }
   ]
 },
};

What really relates to your question here is the test for png, jpg, and gif files which uses the asset/inline module.

The post here https://stackoverflow.com/a/73417058/14358290 explains it with slightly more detail.

Other plugins that copy such files from your /node_modules to /build directory are hacky and create packages that are not really distributable - everyone else that uses the said package would have to set up their Webpack to do the same copying operation. That approach can be avoided now that Webpack 5 solves this problem for us.

Upvotes: 1

Johnny Copperstone
Johnny Copperstone

Reputation: 793

Figured out a way around this.

In my application's webpack config I added a plugin (recommended by @Interrobang) which copies the static assets from the node_module/my-package into the app server's public path:

var TransferWebpackPlugin = require('transfer-webpack-plugin');

...
plugins: [
  new TransferWebpackPlugin([
    { from: 'node_modules/my-package/assets', to: path.join(__dirname, 'my/public') }
  ])
]
...

These will then be made accessible by calling the asset name: localhost:XXXX/my-image.jpg. The server here is basically looking at /my/public/my-image.jpg if you've set it up correctly.

I'm using Express, so I just had to define app.use(express.static('my/public')) in my app server.

Upvotes: 10

Related Questions