package.json中针对节点和浏览器

时间:2016-06-27 13:40:00

标签: javascript reactjs webpack isomorphic-javascript react-starter-kit

在同构反应应用中,我有 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

react-starter-kit使用此方法,但我无法确定此配置的定义位置。

动机

  1. package.json是进行这种分裂的良好语义点。
  2. 客户端软件包仅包含myModule.client.js
  3. 已知的解决方案 - 不是我的答案

    您可以拥有此类文件结构:

    ├── 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 个答案:

答案 0 :(得分:3)

问这个问题已经很久了。我只想澄清以前的答案。

  

如果你看一下React Starter Kit中的tools / webpack.config.js,你会的   看到它导出两个略有不同的Webpack配置,   例如module.exports = [clientConfig,sererConfig]。服务器端   bundle config将此字段目标设置为node(默认情况下为web)。

似乎这个webpack heavyior没有记录,但是当target是'node'时webpack会自动进入'main'条目,当target是'web'时,webpack会自动进入'browser'。

答案 1 :(得分:1)

如果您查看React Starter Kit中的tools/webpack.config.js,您会看到它导出两个略有不同的Webpack配置,例如module.exports = [clientConfig, sererConfig]。服务器端捆绑包配置将此字段target设置为node(默认情况下为web)。

https://webpack.github.io/docs/configuration.html#target

您所描述的方法非常适用于具有完全相同API但实现不同的模块,例如HTTP客户端实用程序在其特定于浏览器的实现中使用XMLHttpRequest且节点http的情况。服务器实现中的模块:

https://github.com/kriasoft/react-starter-kit/tree/master/src/core/fetch

答案 2 :(得分:0)

此处的行为已标准化:https://github.com/defunctzombie/package-browser-field-spec

尽管此规范是非官方的,但许多Javascript捆绑包都遵循它,包括Webpack,Browserify和React Native packager。浏览器字段不仅允许您更改模块入口点,还可以替换或忽略模块中的单个文件。它非常强大。

由于Webpack默认捆绑了Web代码,因此如果要将Webpack用于服务器构建,则需要手动禁用浏览器字段。您可以使用target配置选项执行此操作:https://webpack.js.org/concepts/targets/

答案 3 :(得分:0)

要在节点模块中为客户端和服务器使用不同的入口点,可以使用process.browser标志并对其进行处理

if (process.browser) {
  // load client entry point
} else {
  // load server entry point
}