使用 webpack 和 Express.js 部署 React App
Deploying React App with webpack and Express.js
我正在开发一个应用程序,使用 React 和 webpack 作为客户端的模块打包器。对于服务器,我使用 Express.js.
对于客户端,我使用 webpack.config.dev.js 进行配置,并使用 npm start
在 localhost:3000 启动 Web 服务器。
对于服务器,我有app.js文件,我使用node bin/www
来启动express服务器,但是我必须给一个不同的端口来启动服务器,在这种情况下, localhost:3100.
我只有一个 package.json 用于客户端和服务器。
有没有办法在同一个域中同时部署客户端和服务器?如果是这样,我该怎么做?我想从我的客户端代码中将端点引用为:/data/users,而不是 localhost:3100/data/users.
参见下面的 webpack 配置、快速配置和 package.json(以防有帮助):
webpack.config.dev.js:
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import autoprefixer from 'autoprefixer';
let node_dir = __dirname + '/node_modules';
let app_dir = __dirname + '/app';
export default {
resolve: {
extensions: ['', '.js', '.jsx'],
alias: {
'jquery': node_dir + '/jquery/dist/jquery.js',
'jquery-validation': node_dir + '/jquery-validation/dist/jquery.validate.js',
'jquery-placeholder': node_dir + '/jquery-placeholder/jquery.placeholder.js',
'jquery-oauth': node_dir + '/jquery-oauth/dist/jquery.oauth.js',
'bootstrap': node_dir + '/bootstrap/dist/js/bootstrap.min.js',
'owlcarousel':node_dir+'/owlcarousel/owl-carousel/owl.carousel.min.js',
'modernizr':node_dir + '/npm-modernizr/modernizr.js',
'jquery-shuffle':node_dir + '/shufflejs/dist/shuffle.js' ,
'jquery-stellar':node_dir + '/jquery.stellar/jquery.stellar.js',
'jquery-touchswipe':node_dir + '/jquery-touchswipe/jquery.touchSwipe.min.js'
},
modulesDirectories: ['js', 'node_modules']
},
debug: true,
devtool: 'cheap-module-eval-source-map', // more info:https://webpack.github.io/docs/build-performance.html#sourcemaps and https://webpack.github.io/docs/configuration.html#devtool
noInfo: true, // set to false to see a list of every file being bundled.
context: app_dir + "/app",
entry: {
// must be first entry to properly set public path
app: [app_dir+'/webpack-public-path',
'webpack-hot-middleware/client?reload=true',
app_dir+'/index']
},
target: 'web', // necessary per https://webpack.github.io/docs/testing.html#compile-and-test
output: {
// path: `${__dirname}/src`, // Note: Physical files are only output by the production build task `npm run build`.
path: `${__dirname}/app`,
publicPath: '/',
filename: 'bundle.js'
},
plugins: [
new webpack.ResolverPlugin(
new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin("package.json", ["main"])
),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'), // Tells React to build in either dev or prod modes. https://facebook.github.io/react/downloads.html (See bottom)
__DEV__: true
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new HtmlWebpackPlugin({ // Create HTML file that includes references to bundled CSS and JS.
//template: 'src/index.ejs',
template: app_dir+'/index.html',
minify: {
removeComments: true,
collapseWhitespace: true
},
inject: true
}),
new webpack.ProvidePlugin({
$: "jquery",
jquery: "jquery",
"windows.jQuery": "jquery",
jQuery:"jquery",
}),
new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js', Infinity)
],
module: {
loaders: [
{test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel']},
{test: /\.eot(\?v=\d+.\d+.\d+)?$/, loader: 'file'},
{test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url?limit=10000&mimetype=application/font-woff"},
{test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/octet-stream'},
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=image/svg+xml'},
{test: /\.(jpe?g|png|gif)$/i, loader: 'file?name=[name].[ext]'},
{test: /\.ico$/, loader: 'file?name=[name].[ext]'},
{test: /(\.css|\.scss)$/, loaders: ['style', 'css?sourceMap', 'postcss', 'sass?sourceMap']},
]
},
postcss: ()=> [autoprefixer]
};
app.js 对于服务器端:
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
var users = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', routes);
app.use('/users', users);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app;
和package.json:
{
"name": "dario-webapplication",
"version": "1.0.0",
"description": "Webapplication for Dario project",
"engines": {
"npm": ">=3"
},
"scripts": {
"server": "nodemon ./server/bin/www",
"preinstall": "node tools/nodeVersionCheck.js",
"setup": "node tools/setup/setupMessage.js && npm install && node tools/setup/setup.js",
"remove-demo": "babel-node tools/removeDemo.js",
"start-message": "babel-node tools/startMessage.js",
"prestart": "npm-run-all --parallel start-message remove-dist",
"start": "npm-run-all --parallel open:app lint:watch server",
"open:app": "babel-node tools/srcServer.js",
"open:dist": "babel-node tools/distServer.js",
"lint": "esw webpack.config.* app tools --color",
"lint:watch": "npm run lint -- --watch",
"clean-dist": "npm run remove-dist && mkdir dist",
"remove-dist": "rimraf ./dist",
"prebuild": "npm run clean-dist && npm run lint && npm run test",
"build": "babel-node tools/build.js && npm run open:dist",
"test": "mocha tools/testSetup.js \"app/**/*.spec.js\" --reporter progress",
"test:cover": "babel-node node_modules/isparta/bin/isparta cover --root app --report html node_modules/mocha/bin/_mocha -- --require ./tools/testSetup.js \"app/**/*.spec.js\" --reporter progress",
"test:cover:travis": "babel-node node_modules/isparta/bin/isparta cover --root app --report lcovonly _mocha -- --require ./tools/testSetup.js \"app/**/*.spec.js\" && cat ./coverage/lcov.info | node_modules/coveralls/bin/coveralls.js",
"test:watch": "npm run test -- --watch",
"open:cover": "npm run test:cover && open coverage/index.html"
},
"author": "Francisco Jose Parra Gonzalez",
"license": "MIT",
"dependencies": {
"bootstrap": "3.3.7",
"imports-loader": "0.6.5",
"jquery": "^1.11.1",
"jquery-oauth": "2.1.3",
"jquery-placeholder": "2.3.1",
"jquery-touchswipe": "1.6.15",
"jquery-ui": "^1.10.4",
"jquery-validation": "1.15.1",
"jquery.easing": "^1.3.2",
"jquery.stellar": "0.6.2",
"lightgallery": "1.2.21",
"npm-modernizr": "2.8.3",
"object-assign": "4.1.0",
"owlcarousel": "1.3.3",
"react": "15.3.0",
"react-bootstrap": "0.30.3",
"react-dom": "15.3.0",
"react-redux": "4.4.5",
"react-router": "2.6.1",
"react-router-redux": "4.0.5",
"redux": "3.5.2",
"redux-thunk": "2.1.0",
"shufflejs": "4.0.1",
"throttle-debounce": "1.0.1",
"body-parser": "~1.15.1",
"cookie-parser": "~1.4.3",
"debug": "~2.2.0",
"express": "~4.13.4",
"express-validator": "^2.20.8",
"jade": "~1.11.0",
"morgan": "~1.7.0",
"serve-favicon": "~2.3.0"
},
"devDependencies": {
"autoprefixer": "6.4.0",
"babel-cli": "6.11.4",
"babel-core": "6.11.4",
"babel-loader": "6.2.4",
"babel-plugin-react-display-name": "2.0.0",
"babel-plugin-transform-react-remove-prop-types": "0.2.9",
"babel-preset-es2015": "6.9.0",
"babel-preset-react": "6.11.1",
"babel-preset-react-hmre": "1.1.1",
"babel-preset-stage-1": "6.5.0",
"babel-register": "6.11.6",
"browser-sync": "2.14.0",
"chai": "3.5.0",
"chalk": "1.1.3",
"connect-history-api-fallback": "1.2.0",
"coveralls": "2.11.12",
"cross-env": "2.0.0",
"css-loader": "0.23.1",
"enzyme": "2.4.1",
"eslint": "3.2.2",
"eslint-plugin-import": "1.12.0",
"eslint-plugin-jsx-a11y": "2.0.1",
"eslint-plugin-react": "6.0.0",
"eslint-watch": "2.1.14",
"extract-text-webpack-plugin": "1.0.1",
"file-loader": "0.9.0",
"html-webpack-plugin": "2.22.0",
"isparta": "4.0.0",
"mocha": "3.0.1",
"mockdate": "1.0.4",
"node-sass": "3.8.0",
"npm-run-all": "2.3.0",
"open": "0.0.5",
"postcss-loader": "0.9.1",
"prompt": "1.0.0",
"react-addons-test-utils": "15.3.0",
"redux-immutable-state-invariant": "1.2.3",
"replace": "0.3.0",
"rimraf": "2.5.4",
"sass-loader": "4.0.0",
"sinon": "1.17.5",
"sinon-chai": "2.8.0",
"style-loader": "0.13.1",
"url-loader": "0.5.7",
"webpack": "1.13.1",
"webpack-dev-middleware": "1.6.1",
"webpack-hot-middleware": "2.12.2",
"webpack-md5-hash": "0.0.5"
},
"keywords": [],
"repository": {
"type": "git",
"url": ""
}
}
也许 these 样板可以帮助您。
而第 37 行的 here 就是您要查找的内容。
What initially gets run is bin/server.js, which does little more than enable ES6 and ES7 awesomeness in the server-side node code. It then initiates server.js. In server.js we proxy any requests to /api/* to the API server, running at localhost:3030. All the data fetching calls from the client go to /api/*. Aside from serving the favicon and static content from /static, the only thing server.js does is initiate delegate rendering to react-router. At the bottom of server.js, we listen to port 3000 and initiate the API server.
我正在开发一个应用程序,使用 React 和 webpack 作为客户端的模块打包器。对于服务器,我使用 Express.js.
对于客户端,我使用 webpack.config.dev.js 进行配置,并使用 npm start
在 localhost:3000 启动 Web 服务器。
对于服务器,我有app.js文件,我使用node bin/www
来启动express服务器,但是我必须给一个不同的端口来启动服务器,在这种情况下, localhost:3100.
我只有一个 package.json 用于客户端和服务器。
有没有办法在同一个域中同时部署客户端和服务器?如果是这样,我该怎么做?我想从我的客户端代码中将端点引用为:/data/users,而不是 localhost:3100/data/users.
参见下面的 webpack 配置、快速配置和 package.json(以防有帮助):
webpack.config.dev.js:
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import autoprefixer from 'autoprefixer';
let node_dir = __dirname + '/node_modules';
let app_dir = __dirname + '/app';
export default {
resolve: {
extensions: ['', '.js', '.jsx'],
alias: {
'jquery': node_dir + '/jquery/dist/jquery.js',
'jquery-validation': node_dir + '/jquery-validation/dist/jquery.validate.js',
'jquery-placeholder': node_dir + '/jquery-placeholder/jquery.placeholder.js',
'jquery-oauth': node_dir + '/jquery-oauth/dist/jquery.oauth.js',
'bootstrap': node_dir + '/bootstrap/dist/js/bootstrap.min.js',
'owlcarousel':node_dir+'/owlcarousel/owl-carousel/owl.carousel.min.js',
'modernizr':node_dir + '/npm-modernizr/modernizr.js',
'jquery-shuffle':node_dir + '/shufflejs/dist/shuffle.js' ,
'jquery-stellar':node_dir + '/jquery.stellar/jquery.stellar.js',
'jquery-touchswipe':node_dir + '/jquery-touchswipe/jquery.touchSwipe.min.js'
},
modulesDirectories: ['js', 'node_modules']
},
debug: true,
devtool: 'cheap-module-eval-source-map', // more info:https://webpack.github.io/docs/build-performance.html#sourcemaps and https://webpack.github.io/docs/configuration.html#devtool
noInfo: true, // set to false to see a list of every file being bundled.
context: app_dir + "/app",
entry: {
// must be first entry to properly set public path
app: [app_dir+'/webpack-public-path',
'webpack-hot-middleware/client?reload=true',
app_dir+'/index']
},
target: 'web', // necessary per https://webpack.github.io/docs/testing.html#compile-and-test
output: {
// path: `${__dirname}/src`, // Note: Physical files are only output by the production build task `npm run build`.
path: `${__dirname}/app`,
publicPath: '/',
filename: 'bundle.js'
},
plugins: [
new webpack.ResolverPlugin(
new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin("package.json", ["main"])
),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'), // Tells React to build in either dev or prod modes. https://facebook.github.io/react/downloads.html (See bottom)
__DEV__: true
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new HtmlWebpackPlugin({ // Create HTML file that includes references to bundled CSS and JS.
//template: 'src/index.ejs',
template: app_dir+'/index.html',
minify: {
removeComments: true,
collapseWhitespace: true
},
inject: true
}),
new webpack.ProvidePlugin({
$: "jquery",
jquery: "jquery",
"windows.jQuery": "jquery",
jQuery:"jquery",
}),
new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js', Infinity)
],
module: {
loaders: [
{test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel']},
{test: /\.eot(\?v=\d+.\d+.\d+)?$/, loader: 'file'},
{test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url?limit=10000&mimetype=application/font-woff"},
{test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/octet-stream'},
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=image/svg+xml'},
{test: /\.(jpe?g|png|gif)$/i, loader: 'file?name=[name].[ext]'},
{test: /\.ico$/, loader: 'file?name=[name].[ext]'},
{test: /(\.css|\.scss)$/, loaders: ['style', 'css?sourceMap', 'postcss', 'sass?sourceMap']},
]
},
postcss: ()=> [autoprefixer]
};
app.js 对于服务器端:
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
var users = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', routes);
app.use('/users', users);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app;
和package.json:
{
"name": "dario-webapplication",
"version": "1.0.0",
"description": "Webapplication for Dario project",
"engines": {
"npm": ">=3"
},
"scripts": {
"server": "nodemon ./server/bin/www",
"preinstall": "node tools/nodeVersionCheck.js",
"setup": "node tools/setup/setupMessage.js && npm install && node tools/setup/setup.js",
"remove-demo": "babel-node tools/removeDemo.js",
"start-message": "babel-node tools/startMessage.js",
"prestart": "npm-run-all --parallel start-message remove-dist",
"start": "npm-run-all --parallel open:app lint:watch server",
"open:app": "babel-node tools/srcServer.js",
"open:dist": "babel-node tools/distServer.js",
"lint": "esw webpack.config.* app tools --color",
"lint:watch": "npm run lint -- --watch",
"clean-dist": "npm run remove-dist && mkdir dist",
"remove-dist": "rimraf ./dist",
"prebuild": "npm run clean-dist && npm run lint && npm run test",
"build": "babel-node tools/build.js && npm run open:dist",
"test": "mocha tools/testSetup.js \"app/**/*.spec.js\" --reporter progress",
"test:cover": "babel-node node_modules/isparta/bin/isparta cover --root app --report html node_modules/mocha/bin/_mocha -- --require ./tools/testSetup.js \"app/**/*.spec.js\" --reporter progress",
"test:cover:travis": "babel-node node_modules/isparta/bin/isparta cover --root app --report lcovonly _mocha -- --require ./tools/testSetup.js \"app/**/*.spec.js\" && cat ./coverage/lcov.info | node_modules/coveralls/bin/coveralls.js",
"test:watch": "npm run test -- --watch",
"open:cover": "npm run test:cover && open coverage/index.html"
},
"author": "Francisco Jose Parra Gonzalez",
"license": "MIT",
"dependencies": {
"bootstrap": "3.3.7",
"imports-loader": "0.6.5",
"jquery": "^1.11.1",
"jquery-oauth": "2.1.3",
"jquery-placeholder": "2.3.1",
"jquery-touchswipe": "1.6.15",
"jquery-ui": "^1.10.4",
"jquery-validation": "1.15.1",
"jquery.easing": "^1.3.2",
"jquery.stellar": "0.6.2",
"lightgallery": "1.2.21",
"npm-modernizr": "2.8.3",
"object-assign": "4.1.0",
"owlcarousel": "1.3.3",
"react": "15.3.0",
"react-bootstrap": "0.30.3",
"react-dom": "15.3.0",
"react-redux": "4.4.5",
"react-router": "2.6.1",
"react-router-redux": "4.0.5",
"redux": "3.5.2",
"redux-thunk": "2.1.0",
"shufflejs": "4.0.1",
"throttle-debounce": "1.0.1",
"body-parser": "~1.15.1",
"cookie-parser": "~1.4.3",
"debug": "~2.2.0",
"express": "~4.13.4",
"express-validator": "^2.20.8",
"jade": "~1.11.0",
"morgan": "~1.7.0",
"serve-favicon": "~2.3.0"
},
"devDependencies": {
"autoprefixer": "6.4.0",
"babel-cli": "6.11.4",
"babel-core": "6.11.4",
"babel-loader": "6.2.4",
"babel-plugin-react-display-name": "2.0.0",
"babel-plugin-transform-react-remove-prop-types": "0.2.9",
"babel-preset-es2015": "6.9.0",
"babel-preset-react": "6.11.1",
"babel-preset-react-hmre": "1.1.1",
"babel-preset-stage-1": "6.5.0",
"babel-register": "6.11.6",
"browser-sync": "2.14.0",
"chai": "3.5.0",
"chalk": "1.1.3",
"connect-history-api-fallback": "1.2.0",
"coveralls": "2.11.12",
"cross-env": "2.0.0",
"css-loader": "0.23.1",
"enzyme": "2.4.1",
"eslint": "3.2.2",
"eslint-plugin-import": "1.12.0",
"eslint-plugin-jsx-a11y": "2.0.1",
"eslint-plugin-react": "6.0.0",
"eslint-watch": "2.1.14",
"extract-text-webpack-plugin": "1.0.1",
"file-loader": "0.9.0",
"html-webpack-plugin": "2.22.0",
"isparta": "4.0.0",
"mocha": "3.0.1",
"mockdate": "1.0.4",
"node-sass": "3.8.0",
"npm-run-all": "2.3.0",
"open": "0.0.5",
"postcss-loader": "0.9.1",
"prompt": "1.0.0",
"react-addons-test-utils": "15.3.0",
"redux-immutable-state-invariant": "1.2.3",
"replace": "0.3.0",
"rimraf": "2.5.4",
"sass-loader": "4.0.0",
"sinon": "1.17.5",
"sinon-chai": "2.8.0",
"style-loader": "0.13.1",
"url-loader": "0.5.7",
"webpack": "1.13.1",
"webpack-dev-middleware": "1.6.1",
"webpack-hot-middleware": "2.12.2",
"webpack-md5-hash": "0.0.5"
},
"keywords": [],
"repository": {
"type": "git",
"url": ""
}
}
也许 these 样板可以帮助您。 而第 37 行的 here 就是您要查找的内容。
What initially gets run is bin/server.js, which does little more than enable ES6 and ES7 awesomeness in the server-side node code. It then initiates server.js. In server.js we proxy any requests to /api/* to the API server, running at localhost:3030. All the data fetching calls from the client go to /api/*. Aside from serving the favicon and static content from /static, the only thing server.js does is initiate delegate rendering to react-router. At the bottom of server.js, we listen to port 3000 and initiate the API server.