catamphetamine / webpack-isomorphic-tools

Server-side rendering for your Webpack-built applications (e.g. React)
MIT License
1.25k stars 48 forks source link

`require` doesn't seem to work properly on server. #115

Closed kevinmartin closed 7 years ago

kevinmartin commented 7 years ago

I'm working with an app that is actually multiple apps with some shared code.

Directory structure example of some shared code:

src
├── containers
│   ├── HomeContainer
│   │   └── HomeContainer.js
│   │   └── HomeContainer.app1.js
│   │   └── HomeContainer.app2.js

My webpack is configured with resolve.extensions = extensions: ['', '.json',.${process.env.APP}.js, '.js', '.jsx'].

Then run with APP=app1 env. When an .{app}.js file isn't found, it defaults to .js.

On the client, this works perfectly. On the server, however, it seems to work backwards, attempting to find .js first before .{app}.js.

catamphetamine commented 7 years ago

Oh, resolve.extensions isn't a supported feature. It could be supported theoretically but I see no point in implementing that.

You may consider universal-webpack as an option. https://github.com/halt-hammerzeit/universal-webpack

kevinmartin commented 7 years ago

I did consider universal-webpack, but it's not currently feasible to switch over to that library at this moment.

Any guidance so that I can implement that feature?

catamphetamine commented 7 years ago

Well, I guess, the function to be analysed would be Node.js's Module._findPath: https://github.com/nodejs/node/blob/master/lib/module.js#L150

Module._findPath = function(request, paths, isMain) {
  ...
  var exts; // <------------------ this
  ...

  // For each path
  for (var i = 0; i < paths.length; i++) {
    ...
    if (!trailingSlash) {
      if (rc === 0) {  // File.
        ...
      } else if (rc === 1) {  // Directory.
        if (exts === undefined)
          exts = Object.keys(Module._extensions); // <------------------- this
        filename = tryPackage(basePath, exts, isMain);
      }

      if (!filename) {
        // try it with each of the extensions
        if (exts === undefined)
          exts = Object.keys(Module._extensions); // <------------------- this
        filename = tryExtensions(basePath, exts, isMain);
      }
    }

    if (!filename && rc === 1) {  // Directory.
      if (exts === undefined)
        exts = Object.keys(Module._extensions); // <------------------- this
      filename = tryPackage(basePath, exts, isMain);
    }

    if (!filename && rc === 1) {  // Directory.
      // try it with each of the extensions at "index"
      if (exts === undefined)
        exts = Object.keys(Module._extensions); // <------------------- this
      filename = tryExtensions(path.resolve(basePath, 'index'), exts, isMain);
    }

   ....
}

So, looks like exts is taken from Module._extensions. How is it initialized?

// Native extension for .js
Module._extensions['.js'] = function(module, filename) {
  var content = fs.readFileSync(filename, 'utf8');
  module._compile(internalModule.stripBOM(content), filename);
};

// Native extension for .json
Module._extensions['.json'] = function(module, filename) {
  var content = fs.readFileSync(filename, 'utf8');
  try {
    module.exports = JSON.parse(internalModule.stripBOM(content));
  } catch (err) {
    err.message = filename + ': ' + err.message;
    throw err;
  }
};

So, I guess, one could hack it like this:

for (let extension of resolve.extensions)
{
  if (!Module._extensions[extension])
  {
    Module._extensions[extension] = Module._extensions['.js']
  }
}

Something like this could potentially work.

kevinmartin commented 7 years ago

Excellent. Thanks!

Got it working like this (Node v6.4.0):

const Module = require('module');

Module._extensions = Object.assign({
    [`.${process.env.APP}.js`]: Module._extensions['.js']
}, Module._extensions);