react-redux-webpack-express開発環境構築
42415 ワード
プロジェクト構造
build
|-- webpack.config.js #
|-- webpack.dev.js #
|-- webpack.release.js #
docs #
node_modules
server #expres
src #
|-- conf #
|-- pages #
| |-- page1
| | |-- index.js #
| | |-- index.scss #
| | |-- img #
| | | |-- xx.png
| | |-- __tests__ #
| | | |-- xx.js
| |-- app.html #
| |-- app.js # JS
|-- components #
| |-- loading
| | |-- index.js
| | |-- index.scss
| | |-- __tests__
| | | |-- xx.js
|-- js
| |-- actions
| | |-- index.js
| | |-- __tests__
| | | |-- xx.js
| |-- reducers
| | |-- index.js
| | |-- __tests__
| | | |-- xx.js
| |-- xx.js
|-- css # CSS
| |-- common.scss
|-- img #
| |-- xx.png
tests #
package.json
READNE.md
機能
1.jsx、es 6、scssなどのリソースをコンパイルする.静的リソースを対応するhtmlページ3に自動的に導入する.ブラウザ4をリアルタイムでコンパイルおよびリフレッシュする.指定モジュール化規範に従って自動包装モジュール5.cssにブラウザカーネル接頭辞6を自動的に追加します.必要に応じてjs、css 7をパッケージングする.圧縮js、css、html 8.画像パス処理、圧縮、CssSprite 9.ファイルにhashの名前を付けて、強いキャッシュをします.構文チェック11.指定文字列12をグローバルに置換.ローカルインタフェースシミュレーションサービス13.リモート端末14に公開する.node.jsバックグラウンド
プロジェクトの作成
$ make dir project
$ cd project
$ mkdir build docs src mock tests
$ touch build/webpack.config.js build/webpack.dev.js build/webpack.release.js
// package.json
$ npm init
$ ...
2、最も基本的ないくつかのnpmパッケージをインストールする
$ npm i webpack webpack-dev-server --save-dev
$ npm i react react-dom react-router redux react-redux redux-thunk --save
package.json
{
"name": "react-redux-webpack",
"version": "1.0.0",
"description": " webpact + react + redux + es6 。",
"directories": {
"doc": "docs",
"test": "tests"
},
"scripts": {
"lint": "eslint src",
"test": "make test",
"main": "index.js",
"start": "npm run dev:local",
"dev-start": "node dist/app.js",
"build": "webpack --config ./webpack.deployment.config.js --progress --colors",
"dev:local": "cross-env NODE_ENV=development node build/webpack.dev.js --hot",
"dev:test": "cross-env NODE_ENV=development node build/webpack.dev.js --hot --deploy",
"release": "npm run deploy:online",
"deploy:test": "cross-env NODE_ENV=production node build/webpack.release.js --watch --uglify --deploy=test",
"deploy:online": "cross-env NODE_ENV=production node build/webpack.release.js --uglify --deploy=online"
},
"keywords": [
"webpack",
"react",
"es6",
"babel",
"redux",
"boilerplate"
],
"author": "xiaoyann",
"license": "MIT",
"devDependencies": {
"autoprefixer": "^6.3.7",
"babel-core": "^6.10.4",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.11.1",
"cross-env": "^2.0.0",
"css-loader": "^0.23.1",
"eslint": "^3.1.1",
"eslint-config-airbnb": "^9.0.1",
"eslint-plugin-import": "^1.11.0",
"eslint-plugin-jsx-a11y": "^2.0.1",
"eslint-plugin-react": "^5.2.2",
"extract-text-webpack-plugin": "^1.0.1",
"ftp": "^0.3.10",
"glob": "^7.0.5",
"html-webpack-plugin": "^2.22.0",
"image-webpack-loader": "^2.0.0",
"md5": "^2.1.0",
"node-sass": "^4.5.3",
"postcss-loader": "^0.9.1",
"precss": "^1.4.0",
"react-hot-loader": "^1.3.1",
"sass-loader": "^4.1.1",
"style-loader": "^0.13.2",
"url-loader": "^0.5.7",
"webpack": "^1.13.1",
"webpack-dev-middleware": "^1.11.0",
"webpack-dev-server": "^1.14.1",
"webpack-hot-middleware": "^2.18.2",
"webpack-md5-hash": "0.0.5"
},
"dependencies": {
"d3": "^4.10.0",
"echarts": "^3.6.2",
"node-sass": "^4.5.3",
"react": "^15.2.1",
"react-dom": "^15.2.1",
"react-redux": "^4.4.5",
"react-router": "^2.5.2",
"redux": "^3.5.2",
"redux-thunk": "^2.1.0"
}
}
3、サンプルコードを作成し、最終コードは直接demoを表示する
4、webpackドキュメントに基づいて最も基本的なwebpack構成を作成し、NODE APIを直接使用する方式
/* webpack.config.js */
var webpack = require('webpack');
//
var utils = require('./utils');
var fullPath = utils.fullPath;
var pickFiles = utils.pickFiles;
//
var ROOT_PATH = fullPath('../');
//
var SRC_PATH = ROOT_PATH + '/src';
//
var DIST_PATH = ROOT_PATH + '/dist';
//
var __DEV__ = process.env.NODE_ENV !== 'production';
// conf
var alias = pickFiles({
id: /(conf\/[^\/]+).js$/,
pattern: SRC_PATH + '/conf/*.js'
});
// components
alias = Object.assign(alias, pickFiles({
id: /(components\/[^\/]+)/,
pattern: SRC_PATH + '/components/*/index.js'
}));
// reducers
alias = Object.assign(alias, pickFiles({
id: /(reducers\/[^\/]+).js/,
pattern: SRC_PATH + '/js/reducers/*'
}));
// actions
alias = Object.assign(alias, pickFiles({
id: /(actions\/[^\/]+).js/,
pattern: SRC_PATH + '/js/actions/*'
}));
var config = {
context: SRC_PATH,
entry: {
app: ['./pages/app.js']
},
output: {
path: DIST_PATH,
filename: 'js/bundle.js'
},
module: {},
resolve: {
alias: alias
},
plugins: [
new webpack.DefinePlugin({
// http://stackoverflow.com/questions/30030031/passing-environment-dependent-variables-in-webpack
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV || 'development')
})
]
};
module.exports = config;
/* webpack.dev.js */
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config');
var utils = require('./utils');
var PORT = 8080;
var HOST = utils.getIP();
var args = process.argv;
var hot = args.indexOf('--hot') > -1;
var deploy = args.indexOf('--deploy') > -1;
//
var localPublicPath = 'http://' + HOST + ':' + PORT + '/';
config.output.publicPath = localPublicPath;
config.entry.app.unshift('webpack-dev-server/client?' + localPublicPath);
new WebpackDevServer(webpack(config), {
hot: hot,
inline: true,
compress: true,
stats: {
chunks: false,
children: false,
colors: true
},
// Set this as true if you want to access dev server from arbitrary url.
// This is handy if you are using a html5 router.
historyApiFallback: true,
}).listen(PORT, HOST, function() {
console.log(localPublicPath);
});
上の構成が書けば構築が始まります
$ node build/webpack.dev.js
プロジェクトではjsx、es 6、scssが使用されているため、対応するloaderも追加します.そうしないと、次のようなエラーが発生します.
ERROR in ./src/pages/app.js
Module parse failed: /Users/xiaoyan/working/webpack-react-redux-es6-boilerplate/src/pages/app.js Unexpected token (18:6)
You may need an appropriate loader to handle this file type.
jsx、es 6、scssなどのリソースのコンパイル
ballとbabel-loaderを使用してjsx、es 6インストールプラグインをコンパイルする:babel-preset-es 2015 es 6インストールプラグインを解析するために使用する:babel-preset-react jsxを解析するために使用する
// babel
$ npm i babel-core --save-dev
//
$ npm i babel-preset-es2015 babel-preset-react --save-dev
// loader
$ npm i babel-loader --save-dev
プロジェクトのルートディレクトリに作成します.babelrcファイル:
{
"presets": ["es2015", "react"]
}
Webpackでconfig.jsに追加:
//
var CACHE_PATH = ROOT_PATH + '/cache';
// loaders
config.module.loaders = [];
// babel jsx、es6
config.module.loaders.push({
test: /\.js$/,
exclude: /node_modules/,
include: SRC_PATH,
// loaders , loader
loaders: ['babel?cacheDirectory=' + CACHE_PATH]
});
sass-loader sass:
$ npm i sass-loader node-sass css-loader style-loader --save-dev
css-loader css import
style-loader css
webpack.config.js :
// sass
config.module.loaders.push({
test: /\.(scss|css)$/,
loaders: ['style', 'css', 'sass']
});
静的リソースを対応するhtmlページに自動的に導入
$ npm i html-webpack-plugin --save-dev
Webpackでconfig.jsに追加:
// html
var HtmlwebpackPlugin = require('html-webpack-plugin');
config.plugins.push(
new HtmlwebpackPlugin({
filename: 'index.html',
chunks: ['app'],
template: SRC_PATH + '/pages/app.html'
})
);
これで、プロジェクト全体が正常に走ることができます.
$ node build/webpack.dev.js
ブラウザのリアルタイムコンパイルとリフレッシュ
前の構成が完了すると、プロジェクトはブラウザをリアルタイムでコンパイルし、自動的にリフレッシュできます.次に、react-hot-loaderを使用してホット更新を構成します.
$ npm i react-hot-loader --save-dev
ホットアップデートは開発時にのみ使用するのでwebpack.dev.configには、次のコードが追加されています.
//
if (hot === true) {
config.entry.app.unshift('webpack/hot/only-dev-server');
// loaders[0] .js loader
config.module.loaders[0].loaders.unshift('react-hot');
config.plugins.push(new webpack.HotModuleReplacementPlugin());
}
次のコマンドを実行し、js、cssを変更しようとします.
$ node build/webpack.dev.js --hot
指定されたモジュール化規範に従ってモジュールを自動包装する
WebpackはCommonJS、AMD仕様をサポートし、直接ドキュメントを表示する方法について
cssにブラウザカーネル接頭辞を自動的に追加
postcss-loaderの使用
npm i postcss-loader precss autoprefixer --save-dev
Webpackでconfig.jsに追加:
// sass
config.module.loaders.push({
test: /\.(scss|css)$/,
loaders: ['style', 'css', 'sass', 'postcss']
});
// css autoprefix
var precss = require('precss');
var autoprefixer = require('autoprefixer');
config.postcss = function() {
return [precss, autoprefixer];
}
パッケージ結合js、css
Webpackはデフォルトですべてのモジュールをbundleにパッケージ化し、必要に応じて分割できるCode Splitting機能を提供しています.この例では、フレームワークとライブラリを分割します.
Webpackでconfig.js追加:
config.entry.lib = [
'react', 'react-dom', 'react-router',
'redux', 'react-redux', 'redux-thunk'
]
config.output.filename = 'js/[name].js';
config.plugins.push(
new webpack.optimize.CommonsChunkPlugin('lib', 'js/lib.js')
);
// lib html
// chunks: ['app', 'lib']
CSS:separate css bundleの分割方法
圧縮js、css、html、pngピクチャ
圧縮リソースは、生産環境でのみ使用することが望ましい
// js、css
config.plugins.push(
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
);
// html
// html
var HtmlwebpackPlugin = require('html-webpack-plugin');
config.plugins.push(
new HtmlwebpackPlugin({
filename: 'index.html',
chunks: ['app', 'lib'],
template: SRC_PATH + '/pages/app.html',
minify: {
collapseWhitespace: true,
collapseInlineTagWhitespace: true,
removeRedundantAttributes: true,
removeEmptyAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
removeComments: true
}
})
);
画像パス処理、圧縮、CssSprite
$ npm i url-loader image-webpack-loader --save-dev
Webpackでconfig.jsに追加:
// ,
config.module.loaders.push({
test: /\.(?:jpg|gif|png|svg)$/,
loaders: [
'url?limit=8000&name=img/[hash].[ext]',
'image-webpack'
]
});
雪碧図処理:webpack_auto_sprites
ファイルにhashの名前を付けて、強いキャッシュをします
docsにより、産出ファイル名に[hash]を付ける
config.output.filename = 'js/[name].[hash].js';
ローカルインタフェースシミュレーションサービス
//epxressを直接使用してローカルサービスを作成する
$ npm install epxress --save-dev
$ mkdir mock && cd mock
$ touch app.js
var express = require('express');
var app = express();
//ドメイン間アクセスを設定し、開発しやすい
app.all('*', function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
next();
});
//具体的なインタフェース設定
app.get('/api/test', function(req, res) {
res.send({ code: 200, data: 'your data' });
});
var server = app.listen(3000, function() {
var host = server.address().address;
var port = server.address().port;
console.log('Mock server listening at http://%s:%s', host, port);
});
// , PM2 ,
$ node app.js &
リモートに公開
deployプラグインを書いてftpでファイルをアップロードします
$ npm i ftp --save-dev
$ touch build/deploy.plugin.js
// build/deploy.plugin.js
var Client = require('ftp');
var client = new Client();
//
var __assets__ = [];
//
var __connected__ = false;
var __conf__ = null;
function uploadFile(startTime) {
var file = __assets__.shift();
//
if (!file) return client.end();
//
client.put(file.source, file.remotePath, function(err) {
//
var timming = Date.now() - startTime;
if (err) {
console.log('error ', err);
console.log('upload fail -', file.remotePath);
} else {
console.log('upload success -', file.remotePath, timming + 'ms');
}
// ,
if (__assets__.length === 0) {
client.end();
} else {
uploadFile();
}
});
}
//
function connect(conf) {
if (!__connected__) {
client.connect(__conf__);
}
}
//
client.on('ready', function() {
__connected__ = true;
uploadFile(Date.now());
});
//
client.on('close', function() {
__connected__ = false;
// ,
if (__assets__.length > 0) connect();
});
/**
* [deploy description]
* @param {Array} assets deploy
* file.source buffer
* file.remotePath path
*/
function deployWithFtp(conf, assets, callback) {
__conf__ = conf;
__assets__ = __assets__.concat(assets);
connect();
}
var path = require('path');
/**
* [DeployPlugin description]
* @param {Array} options
* option.reg
* option.to
*/
function DeployPlugin(conf, options) {
this.conf = conf;
this.options = options;
}
DeployPlugin.prototype.apply = function(compiler) {
var conf = this.conf;
var options = this.options;
compiler.plugin('done', function(stats) {
var files = [];
var assets = stats.compilation.assets;
for (var name in assets) {
options.map(function(cfg) {
if (cfg.reg.test(name)) {
files.push({
localPath: name,
remotePath: path.join(cfg.to, name),
source: new Buffer(assets[name].source(), 'utf-8')
});
}
});
}
deployWithFtp(conf, files);
});
};
module.exports = DeployPlugin;
上に書いたプラグインを用いて、ローカル、テスト環境で同時に開発を実現し、自動的にリフレッシュとホット更新を実現します.Webpackでdev.jsに追加:
var DeployPlugin = require('./deploy.plugin');
//
if (deploy === true) {
config.plugins.push(
new DeployPlugin({
user: 'username',
password: 'password',
host: 'your host',
keepalive: 10000000
},
[{reg: /html$/, to: '/xxx/xxx/xxx/app/views/'}])
);
}
この例ではhtmlファイルのみをテスト環境にパブリッシュし、静的リソースはローカルのwebpack-dev-serverを使用するので、ホット更新、自動リフレッシュは正常に使用できます.
その他のパブリケーションプラグイン:
deploy-kit ( )
sftp-webpack-plugin
webpack-sftp-client
Webpack問題と最適化
コードを変更するとすべてのchunkhashが変更されます
このプロジェクトでは、フレームワークとライブラリをchunkにパッケージ化しました.この部分は自分では変更しませんが、ビジネスコードを変更すると、このchunkのhashは同時に変化しました.これにより、オンラインになると、ユーザーはまったく変化していないファイルを再ダウンロードしなければなりません.
Webpackが提供するchunkhashを使用してファイルの名前を付けることはできません.では、ファイルの内容に基づいてhashの名前を計算すればいいのではないでしょうか.開発時にhashを使用する必要はなく、hashを使用しても問題ありません.最終的に生産された場合、私たちは自分の方法で名前を変更します.
$ npm i md5 --save-dev
$ touch build/rename.plugin.js
// rename.plugin.js
var fs = require('fs');
var path = require('path');
var md5 = require('md5');
function RenamePlugin() {
}
RenamePlugin.prototype.apply = function(compiler) {
compiler.plugin('done', function(stats) {
var htmlFiles = [];
var hashFiles = [];
var assets = stats.compilation.assets;
Object.keys(assets).forEach(function(fileName) {
var file = assets[fileName];
if (/\.(css|js)$/.test(fileName)) {
var hash = md5(file.source());
var newName = fileName.replace(/(.js|.css)$/, '.' + hash + '$1');
hashFiles.push({
originName: fileName,
hashName: newName
});
fs.rename(file.existsAt, file.existsAt.replace(fileName, newName));
}
else if (/\.html$/) {
htmlFiles.push(fileName);
}
});
htmlFiles.forEach(function(fileName) {
var file = assets[fileName];
var contents = file.source();
hashFiles.forEach(function(item) {
contents = contents.replace(item.originName, item.hashName);
});
fs.writeFile(file.existsAt, contents, 'utf-8');
});
});
};
module.exports = RenamePlugin;
Webpackでrelease.jsに追加:
// webpack.release.js
var RenamePlugin = require('./rename.plugin');
config.plugins.push(new RenamePlugin());