[英]Different main entry point in package.json for node and browser
In isomorphic react app I have myModule
which should behave differently on node and browser environments.在同构反应应用程序中,我有
myModule
,它在节点和浏览器环境中的行为应该有所不同。 I would like configure this split point in package.json
for myModule
:我想在
package.json
为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
:所以当我在node 中使用
myModule
,我应该只得到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
:在浏览器端应该只使用
myModule.client.js
构建 bundle :
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. react-starter-kit使用这种方法,但我不知道这个配置在哪里定义。
package.json
is good semantic point to do this kind of splitting. package.json
是进行这种拆分的良好语义点。myModule.client.js
.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
:在
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 .这样做的主要问题是客户端包包含大量繁重的 kB 后端代码。
I include my webpack.config.js
.我包括我的
webpack.config.js
。 Strangely this config always point to myModule.client.js
for browser and node.奇怪的是,这个配置总是指向浏览器和节点的
myModule.client.js
。
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行为在这里标准化: 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.尽管该规范是非官方的,但许多 Javascript 打包器都遵循它,包括 Webpack、Browserify 和 React Native 打包器。 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.由于 Webpack 默认为 Web 捆绑代码,如果您想将 Webpack 用于您的服务器构建,您需要手动禁用浏览器字段。 You can do that using the
target
config option to do this: https://webpack.js.org/concepts/targets/您可以使用
target
配置选项来做到这一点: https : //webpack.js.org/concepts/targets/
It has been a long time since this question was asked.问这个问题已经很长时间了。 I just want to clarify the previous answer.
我只是想澄清以前的答案。
If you look at tools/webpack.config.js in React Starter Kit you will see that it exports two Webpack configurations that slightly differ, eg module.exports = [clientConfig, sererConfig].
如果您查看 React Starter Kit 中的 tools/webpack.config.js,您会看到它导出了两个略有不同的 Webpack 配置,例如 module.exports = [clientConfig, sererConfig]。 The server-side bundle config has this field target set to node (by default it's web).
服务器端捆绑配置将此字段目标设置为节点(默认情况下它是网络)。
It seems this webpack beheavior is not documented, but webpack automatically takes 'main' entry when target is 'node' and takes 'browser' entry when target is 'web'.似乎没有记录此 webpack 行为,但是当目标是“节点”时,webpack 会自动获取“主”条目,而当目标是“web”时,它会自动获取“浏览器”条目。
If you look at tools/webpack.config.js
in React Starter Kit you will see that it exports two Webpack configurations that slightly differ, eg module.exports = [clientConfig, sererConfig]
.如果您查看React Starter Kit中的
tools/webpack.config.js
,您会看到它导出了两个略有不同的 Webpack 配置,例如module.exports = [clientConfig, sererConfig]
。 The server-side bundle config has this field target
set to node
(by default it's web
).服务器端捆绑配置将此字段
target
设置为node
(默认情况下为web
)。
https://webpack.github.io/docs/configuration.html#target https://webpack.github.io/docs/configuration.html#target
The approach that you described works great for modules that have exactly the same API but different implementations, like in the case with HTTP client utility that uses XMLHttpRequest
in its browser-specific implementation and Node's http
module in its server implementation:您描述的方法非常适用于具有完全相同 API 但实现不同的模块,例如 HTTP 客户端实用程序在其特定于浏览器的实现中使用
XMLHttpRequest
以及在其服务器实现中使用 Node 的http
模块的情况:
https://github.com/kriasoft/react-starter-kit/tree/master/src/core/fetch https://github.com/kriasoft/react-starter-kit/tree/master/src/core/fetch
To have a different entry point for client and server in a Node Module, you can use process.browser
flag and handle the same要在节点模块中为客户端和服务器设置不同的入口点,您可以使用
process.browser
标志并处理相同的
if (process.browser) {
// load client entry point
} else {
// load server entry point
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.