簡體   English   中英

package.json 中節點和瀏覽器的不同主要入口點

[英]Different main entry point in package.json for node and browser

在同構反應應用程序中,我有myModule ,它在節點和瀏覽器環境中的行為應該有所不同。 我想在package.jsonmyModule配置這個分割點:

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

所以當我在node 中使用myModule ,我應該只得到myModule.server.js

server.js

import myModule from './myModule';
myModule(); // invoke myModule.server.js

瀏覽器端應該只使用myModule.client.js構建 bundle

browser.js

import myModule from './myModule';
myModule(); // invoke myModule.client.js

react-starter-kit使用這種方法,但我不知道這個配置在哪里定義。


動機

  1. package.json是進行這種拆分的良好語義點。
  2. 客戶端包只包含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');
}

這樣做的主要問題是客戶端包包含大量繁重的 kB 后端代碼


我的 webpack 配置

我包括我的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,

    })
];

行為在這里標准化: https : //github.com/defunctzombie/package-browser-field-spec

盡管該規范是非官方的,但許多 Javascript 打包器都遵循它,包括 Webpack、Browserify 和 React Native 打包器。 瀏覽器字段不僅允許您更改模塊入口點,還可以替換或忽略模塊中的單個文件。 它非常強大。

由於 Webpack 默認為 Web 捆綁代碼,如果您想將 Webpack 用於您的服務器構建,您需要手動禁用瀏覽器字段。 您可以使用target配置選項來做到這一點: https : //webpack.js.org/concepts/targets/

問這個問題已經很長時間了。 我只是想澄清以前的答案。

如果您查看 React Starter Kit 中的 tools/webpack.config.js,您會看到它導出了兩個略有不同的 Webpack 配置,例如 module.exports = [clientConfig, sererConfig]。 服務器端捆綁配置將此字段目標設置為節點(默認情況下它是網絡)。

似乎沒有記錄此 webpack 行為,但是當目標是“節點”時,webpack 會自動獲取“主”條目,而當目標是“web”時,它會自動獲取“瀏覽器”條目。

如果您查看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以及在其服務器實現中使用 Node 的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
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM