jedwards1211 / meteor-webpack-react

(DEPRECATED) use jedwards1211/crater instead
https://github.com/jedwards1211/crater
374 stars 75 forks source link

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

Open jthomaschewski opened 9 years ago

jthomaschewski commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

can you paste your dev and prod webpack config?

jedwards1211 commented 9 years ago

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

jthomaschewski commented 9 years ago

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} />
....

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

jedwards1211 commented 8 years ago

@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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

@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 commented 8 years ago

@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.