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ページに自動的に導入

  • html-webpack-plugin
  • を使用
    $ 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

  • 圧縮画像image-webpack-loader
  • を使用
  • ピクチャパス処理url-loader
  • を用いる
    $ 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());