Include assets from webpack bundled npm package
Asked Answered
O

2

29

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'
      }
    ]
  }
};
Olpe answered 11/4, 2016 at 21:45 Comment(3)
If the images aren't imported or required, webpack won't process them. You probably want something like the copy-webpack-pluginVivianaviviane
Thanks @Interrobang. I'm a bit uncertain about taking this approach, as it seems like the webpack build step in the npm package would need to know about the public path of my application. Is there any option in my app's webpack config which would resolve the paths again after I copy the assets?Olpe
Got it, will post the fix. Thanks @Interrobang!Olpe
O
10

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.

Olpe answered 12/4, 2016 at 10:27 Comment(2)
But will this work if you are creating an NPM module that someone may pull in to an arbitrary web app? Let's say that ultimately their web app is served from theirserver.com/subfolder rather than from the root of the server, how would webpack resolve to the correct absolute path?Nabors
exactly how does it scale.Yarndyed
K
1

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://mcmap.net/q/502722/-npm-package-include-images-in-css 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.

Kurt answered 22/8, 2022 at 8:54 Comment(1)
Hi @Oguz i also stumbled upon same situation for must using exportType: "css-style-sheet" when creating css as CSSSTYLESHEET object to use inside shadow dom which is returned from my package and used consumer side. Do you have any workaround even for this in order for not to do any configuration there?Tass

© 2022 - 2024 — McMap. All rights reserved.