Различные основные точки входа в package.json для узла и браузера
В приложении изоморфной реакции у меня есть 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
Поэтому, когда я использую myModule
в узле я должен получить только myModule.server.js
:
server.js
import myModule from './myModule';
myModule(); // invoke myModule.server.js
На стороне браузера следует строить связку только с myModule.client.js
:
browser.js
import myModule from './myModule';
myModule(); // invoke myModule.client.js
реактивный стартовый комплект использует этот подход, но я не могу понять, где определена эта конфигурация.
мотивация
package.json
это хорошая семантическая точка для такого разделения.- Клиентский пакет содержит только
myModule.client.js
,
Известное решение - не ответ для меня
Вы можете иметь такую структуру файлов:
├── myModule
│ ├── myModule.client.js
│ ├── myModule.server.js
│ └── index.js <-- difference
│
├── browser.js
└── server.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');
}
Основная проблема заключается в том, что клиентский пакет содержит много тяжелого внутреннего кода в килобайтах.
Моя конфигурация веб-пакета
Я включаю мой webpack.config.js
, Странно этот конфиг всегда указывает на 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,
})
];
4 ответа
Поведение стандартизировано здесь: https://github.com/defunctzombie/package-browser-field-spec
Хотя эта спецификация является неофициальной, за ней следуют многие упаковщики Javascript, включая Webpack, Browserify и упаковщик React Native. Поле браузера не только позволяет вам изменить точку входа вашего модуля, но также заменить или игнорировать отдельные файлы в вашем модуле. Это довольно мощный.
Поскольку Webpack по умолчанию связывает код для Интернета, вам нужно вручную отключить поле браузера, если вы хотите использовать Webpack для сборки вашего сервера. Вы можете сделать это, используя target
опция конфигурации для этого: https://webpack.js.org/concepts/targets/
Прошло много времени с тех пор, как был задан этот вопрос. Я просто хочу уточнить предыдущий ответ.
Если вы посмотрите на tools / webpack.config.js в React Starter Kit, то увидите, что он экспортирует две слегка отличающиеся конфигурации Webpack, например, module.exports = [clientConfig, sererConfig]. Конфигурация комплекта на стороне сервера имеет целевое поле, установленное на узел (по умолчанию это web).
Кажется, что этот веб-пакет не задокументирован, но веб-пакет автоматически принимает запись "main", когда target - "node", и принимает запись "browser", когда target - "web".
Если вы посмотрите на tools/webpack.config.js
в React Starter Kit вы увидите, что он экспортирует две конфигурации Webpack, которые немного отличаются, например module.exports = [clientConfig, sererConfig]
, Поле конфигурации на стороне сервера имеет это поле target
установлен в node
(по умолчанию это web
).
https://webpack.github.io/docs/configuration.html
Описанный вами подход отлично работает для модулей, которые имеют точно такой же API, но разные реализации, как в случае с клиентской утилитой HTTP, которая использует XMLHttpRequest
в его специфичной для браузера реализации и Node's http
Модуль в своей серверной реализации:
https://github.com/kriasoft/react-starter-kit/tree/master/src/core/fetch
Чтобы иметь разные точки входа для клиента и сервера в модуле узла, вы можете использовать process.browser
помечать и обрабатывать то же самое
if (process.browser) {
// load client entry point
} else {
// load server entry point
}