Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loading assets (like images) by url with webpack #62

Open
jthomaschewski opened this issue Sep 27, 2015 · 12 comments
Open

Loading assets (like images) by url with webpack #62

jthomaschewski opened this issue Sep 27, 2015 · 12 comments

Comments

@jthomaschewski
Copy link
Contributor

I'm trying to load some images via webpack by using url-loader like that:

{ test: /\.png$/, loader: 'url-loader?limit=10000&mimetype=image/png' }

In dev mode imported images get converted to a url like http://0.0.0.0:9090/assets/7de2d4d25d8c70839ae06de9c36ed886.png which seems to be correct. But the webpack-dev server returns an empty response.
No files are generated anywhere in ./webpack/assets or ./meteor_core

In prod mode imported images are generated and saved to e.g ./webpack/assets/7de2d4d25d8c70839ae06de9c36ed886.png

But urls point to meteor server like http://127.0.0.1:3000/assets/7de2d4d25d8c70839ae06de9c36ed886.png and obviously the generated files are not accessible.

Of course it's possible to access images which are stored in ./meteor_core/public/ - but I'd like to use webpack loaders to have goodies like automatically generated base64 strings for small images, minification...

Is there any recommended way of loading assets by url with this skeleton?

@tomitrescak
Copy link

I have exactly the same problem. Css loader in prod is resolving backgrounds to the assets/... path, which is not visible in prod. The possible solution is to put CSS files in the meteor_core directoy, losing the hot code push ... not optimal ;(

@jedwards1211
Copy link
Owner

can you paste your dev and prod webpack config?

@jedwards1211
Copy link
Owner

@JBBr have you changed path or publicPath anywhere in your webpack config? In this project the assets should be saved to /assets/...

@jthomaschewski
Copy link
Contributor Author

I can reproduce this with the latest master:

  1. Add file loader to webpack.config.client.js
    { test: /\.jpg$/, loader: 'file' },
  2. Load image in App.jsx
import image from '../images/image.jpg';
....
<img src={image} />
....
  • When using ./dev:
    Everything works (In this case I don't get an empty response anymore).
    resulting src="http://0.0.0.0:9090/assets/44bae156f12e5089150d096a2cbf83c1.jpg"

  • When using ./prod:
    Image url does not exist.
    result src="/assets/44bae156f12e5089150d096a2cbf83c1.jpg" (http://127.0.0.1:3000/assets/44bae156f12e5089150d096a2cbf83c1.jpg)

    The file exists in webpack/assets/ but is not shipped by the meteor server in ./prod mode.

Bad workaround: symbolic link from meteor_core/public/assets to webpack/assets. But then also server.bundle.js is published to the world...

@jedwards1211
Copy link
Owner

@JBBr thought I had commented, we can solve this by simply making different asset directories for server and client (or even just outputting straight into the Meteor dirs)

@jedwards1211
Copy link
Owner

Errr...but Meteor very unfortunately would auto-load the client.bundle.js from the asset directory as well. Maybe we'll have to go with a package.

@jedwards1211
Copy link
Owner

Actually I think I can solve with separate client and server output dirs, and making meteor_core/client a symlink to webpack's client output directory.

Yesterday I was experimenting with webpack-dev-server's proxy: { '*': 'http://localhost:3000' } option, hoping I would be able to open the webpage from localhost:9090, but unfortunately Meteor's sockjs wasn't connecting...didn't too much surprise me...

@TeemoWan
Copy link

I solved it.

1.Add folder /meteor_core/public

2.modify prod.js

require('shelljs/global');
if (!process.env.NODE_ENV) {
  process.env.NODE_ENV = env.NODE_ENV = 'production';
}

var fs = require('fs');
var path = require('path');
var dirs = require('./dirs');
var webpack = require('webpack');
var addProgressPlugin = require('./addProgressPlugin');
var statsOptions = require('./statsOptions');

var serverConfig = require(path.join(dirs.webpack, 'webpack.config.server.prod'));
var clientConfig = require(path.join(dirs.webpack, 'webpack.config.client.prod'));

addProgressPlugin(serverConfig);
addProgressPlugin(clientConfig);

serverConfig.plugins.push(new webpack.BannerPlugin('var require = Npm.require;\n', {raw: true}));

var serverBundlePath = path.join(dirs.assets, 'server.bundle.js');
var clientBundlePath = path.join(dirs.assets, 'client.bundle.js');
var serverBundleLink = path.join(dirs.meteor, 'server/server.bundle.min.js');
var clientBundleLink = path.join(dirs.meteor, 'client/client.bundle.min.js');
var loadClientBundleHtml = path.join(dirs.webpack, 'loadClientBundle.html');
var loadClientBundleLink = path.join(dirs.meteor, 'client/loadClientBundle.html');
var requireServerBundleJs = path.join(dirs.meteor, 'server/require.server.bundle.js');

var clientAssetsPath = dirs.assets;     //add
var clientAssetsLink = path.join(dirs.meteor, 'public/assets'); //add 

exec('node core-js-custom-build.js');

if (fs.existsSync(loadClientBundleLink)) rm(loadClientBundleLink);
if (fs.existsSync(requireServerBundleJs)) rm(requireServerBundleJs);

var serverCompiler = webpack(serverConfig);
var serverBundleReady = false;
var clientBundleReady = false;

serverCompiler.watch(serverConfig.watchOptions || {}, function(err, stats) {
  console.log(stats.toString(statsOptions));
  if (!serverBundleReady) {
    serverBundleReady = true;
    ln('-sf', serverBundlePath, serverBundleLink);
    compileClient();
  }
});

function compileClient() {
  var clientCompiler = webpack(clientConfig);
  clientCompiler.watch(clientConfig.watchOptions || {}, function(err, stats) {
    console.log(stats.toString(statsOptions));
    if (!clientBundleReady) {
      clientBundleReady = true;
      ln('-sf', clientBundlePath, clientBundleLink);
      ln('-sf', clientAssetsPath, clientAssetsLink);   // add
      runMeteor();
    }
  });
}

function runMeteor() {
  cd(dirs.meteor);
  exec('meteor run --production --settings ../settings/prod.json', {async: true});
}

@defrex
Copy link

defrex commented Jan 22, 2016

Warning: the above is likely insecure.

@TeemoWan That assets folder also includes your server.bundle.js file. This approach will expose that to anyone who knows what to look for. That file will contain all your server-only code, which should likely remain secret.

@defrex
Copy link

defrex commented Jan 22, 2016

I came up with a slightly safer way of doing the same thing. It white-lists some static file types and only symlinks those. It seems like there should be a more elegant solution, but this gets the job done without too much hassle.

Some constants.

var staticAssetPath = path.join(dirs.meteor, 'public/assets');
var staticAssetWhitelist = ['png', 'jpg', 'svg', 'eot', 'woff', 'ttf', 'woff2'];

And for the linking: put the following in the compileClient callback right before runMeteor() in prod.js or return callback(); in deploy.js.

fs.readdirSync(dirs.assets).filter(function(file) {
  return staticAssetWhitelist.indexOf(file.split('.').slice(-1)[0].toLowerCase()) != -1;
}).forEach(function(staticFile) {
  ln('-sf', path.join(dirs.assets, staticFile), path.join(staticAssetPath, staticFile));
});

@bpartridge
Copy link

@defrex That should work for an unchanging list of static assets, but if you add a new asset, you'd need to restart prod.js, right? Any downsides to running this even when clientBundleReady is set?

@defrex
Copy link

defrex commented Feb 18, 2016

@bpartridge It shouldn't matter. You'll need to attach it to a file watcher or something if you don't want to restart the script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants