In isomorphic react app I have myModule
which should behave differently on node and browser environments. I would like configure this split point in package.json
for myModule
:
package.json
{
"private": true,
"name": "myModule",
"main": "./myModule.server.js",
"browser": "./myModule.client.js"
}
file structure
├── myModule
│ ├── myModule.client.js
│ ├── myModule.server.js
│ └── package.json
│
├── browser.js
└── server.js
So when I use myModule
in node I should get only myModule.server.js
:
server.js
import myModule from './myModule';
myModule(); // invoke myModule.server.js
On the browser side should build bundle only with myModule.client.js
:
browser.js
import myModule from './myModule';
myModule(); // invoke myModule.client.js
react-starter-kit uses this approach but I can't figure out where is this configuration defined.
package.json
is good semantic point to do this kind of splitting.myModule.client.js
.You can have this kind of file structure:
├── myModule
│ ├── myModule.client.js
│ ├── myModule.server.js
│ └── index.js <-- difference
│
├── browser.js
└── server.js
And in index.js
:
if (process.browser) { // this condition can be different but you get the point
module.exports = require('./myModule.client');
} else {
module.exports = require('./myModule.server');
}
The main problem with this is that client bundle contains a lot of heavy kB backend code.
I include my webpack.config.js
. Strangely this config always point to myModule.client.js
for browser and node.
const webpack = require('webpack');
var path = require('path');
var fs = require('fs');
const DEBUG = !process.argv.includes('--release');
const VERBOSE = !process.argv.includes('--verbose');
const AUTOPREFIXER_BROWSERS = [
'Android 2.3',
'Android >= 4',
'Chrome >= 35',
'Firefox >= 31',
'Explorer >= 9',
'iOS >= 7',
'Opera >= 12',
'Safari >= 7.1',
];
let nodeModules = {};
fs.readdirSync('node_modules')
.filter(function(x) {
return ['.bin'].indexOf(x) === -1 ;
})
.forEach(function(mod) {
nodeModules[mod] = 'commonjs ' + mod;
});
let loaders = [
{
exclude: /node_modules/,
loader: 'babel'
},
{
test: [/\.scss$/,/\.css$/],
loaders: [
'isomorphic-style-loader',
`css-loader?${DEBUG ? 'sourceMap&' : 'minimize&'}modules&localIdentName=` +
`${DEBUG ? '[name]_[local]_[hash:base64:3]' : '[hash:base64:4]'}`,
'postcss-loader?parser=postcss-scss'
]
},
{
test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/,
loader: 'url-loader',
query: {
name: DEBUG ? '[name].[ext]' : '[hash].[ext]',
limit: 10000,
},
},
{
test: /\.(eot|ttf|wav|mp3)$/,
loader: 'file-loader',
query: {
name: DEBUG ? '[name].[ext]' : '[hash].[ext]',
},
},
{
test: /\.json$/,
loader: 'json-loader',
},
];
const common = {
module: {
loaders
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
],
postcss: function plugins(bundler) {
var plugins = [
require('postcss-import')({ addDependencyTo: bundler }),
require('precss')(),
require('autoprefixer')({ browsers: AUTOPREFIXER_BROWSERS }),
];
return plugins;
},
resolve: {
root: path.resolve(__dirname, 'src'),
extensions: ['', '.js', '.jsx', '.json']
}
};
module.exports = [
Object.assign({} , common, { // client
entry: [
'babel-polyfill',
'./src/client.js'
],
output: {
path: __dirname + '/public/',
filename: 'bundle.js'
},
target: 'web',
node: {
fs: 'empty',
},
devtool: DEBUG ? 'cheap-module-eval-source-map' : false,
plugins: [
...common.plugins,
new webpack.DefinePlugin({'process.env.BROWSER': true }),
],
}),
Object.assign({} , common, { // server
entry: [
'babel-polyfill',
'./src/server.js'
],
output: {
path: __dirname + '',
filename: 'server.js'
},
target: 'node',
plugins: [
...common.plugins,
new webpack.DefinePlugin({'process.env.BROWSER': false }),
],
node: {
console: false,
global: false,
process: false,
Buffer: false,
__filename: false,
__dirname: false,
},
externals: nodeModules,
})
];
The behavior is standardized here: https://github.com/defunctzombie/package-browser-field-spec
Although this specification is unofficial, many Javascript bundlers follow it, including Webpack, Browserify, and the React Native packager. The browser field not only allows you to change your module entry point, but to also replace or ignore individual files within your module. It's quite powerful.
Since Webpack bundles code for the web by default, you need to manually disable the browser field if you want to use Webpack for your server build. You can do that using the target
config option to do this: https://webpack.js.org/concepts/targets/