首页 > 代码库 > npm scripts + webpack 实践经验(React、Nodejs)

npm scripts + webpack 实践经验(React、Nodejs)

  最近用Webpack+npm scripts+Mongodb+Nodejs+React写了个后台项目,在用Webpack构建过程中遇到了许多坑,就写出来分享一下。

  构建工具五花八门,想当年刚学会Grunt,Grunt就被淘汰了,取而代之的是Gulp,其任务流式的机制,有着逻辑清晰,灵活多变的特点,而且容易上手,相比Grunt真的要少写太多配置文件代码了,立马就学的风声水起,刚熟练Gulp,Webpack又如构建工具界的一颗新星冉冉升起,其独特的模块打包机制和各种各样好用的loader,让无数Coder为之青睐,加之和React,ES6的完美配合,博主又立马放弃Gulp,抱着怀疑的心态尝试纯用Webpack构建一个项目(注意:Webpack和Gulp并不是冲突的,曾在项目中结合使用过,但博主决定试一试完全不用Gulp是否可行),结果当然是肯定的,Gulp有的东西(压缩,合并,MD5)等等,你几乎都可以用Webpack来实现一遍,再配上npm scripts,简直如虎添翼。

  首先我简单介绍一下npm scripts。先来看一段代码。

{
  "name": "app",
  "version": "0.0.1",
  "private": true,
  "main": "./bin/www",
  "scripts": {
    "clean": "rm -rf client/dist/*",
    "copy": "rsync -a --exclude=*.html --exclude=*.jsx ./client/src/*.* ./client/dist",
    "start": "./bin/www",
    "server": "node server.js",
    "build": "npm run clean && webpack --config webpack.config.pro.js && npm run copy && node qiniu.js"
  },
  "dependencies": {
    "babel-runtime": "^6.11.6",
    "body-parser": "~1.15.1",
    "bootstrap-sass": "^3.3.7",
    "classnames": "^2.2.5",
    "cookie-parser": "~1.4.3",
    "debug": "~2.2.0",
    "ejs": "~2.4.1",
    "express": "~4.13.4",
    "mongoose": "^4.6.4",
    "morgan": "~1.7.0",
    "react": "^15.3.2",
    "react-dom": "^15.3.2",
    "react-redux": "^4.4.5",
    "react-router": "^2.8.1",
    "redux": "^3.6.0",
    "serve-favicon": "~2.3.0"
  },
  "devDependencies": {
    "autoprefixer": "^6.5.1",
    "babel-core": "^6.17.0",
    "babel-loader": "^6.2.5",
    "babel-plugin-transform-runtime": "^6.15.0",
    "babel-preset-es2015": "^6.16.0",
    "babel-preset-react": "^6.16.0",
    "css-loader": "^0.25.0",
    "cssnano": "^3.7.7",
    "extract-text-webpack-plugin": "^1.0.1",
    "file-loader": "^0.9.0",
    "html-webpack-plugin": "^2.24.0",
    "node-sass": "^3.10.1",
    "postcss-loader": "^1.0.0",
    "qiniu": "^6.1.13",
    "react-hot-loader": "^3.0.0-beta.6",
    "sass-loader": "^4.0.2",
    "style-loader": "^0.13.1",
    "url-loader": "^0.5.7",
    "webpack": "^1.13.2",
    "webpack-dev-server": "^1.16.2",
    "webpack-md5-hash": "0.0.5"
  }
}

  做前端的童鞋们不可能不接触这个配置文件package.json,是npm帮助我们管理依赖的重要配置文件,其中的scripts那一块,就是npm scripts的使用方式啦,凡是在npm的scripts属性中配置的键值对,都可以通过npm run xxx【xxx为键名】来执行对应的值里面的命令,比如:npm run server,就会执行node server.js,npm scripts支持bash shell。是不是有点熟悉?你可以把多个命令配置在一个键名下,通过&&符号连接,这样执行完第一个,就会执行第二个,以此类推,直到最后一个执行完毕就结束运行,如果你想同时并行执行,可以用一个&符号,不过貌似只有bash支持,你可以通过npm-run-all插件或者parallelshell插件来做到并行执行。

  废话太多了,接下来开始说Webpack,不得不再废话一句,我们以前使用gulp的时候一般也会配置两套任务流,开发环境和生产环境,webpack当然也可以做到,只不过不是像gulp那样用任务的方式自由组合,而是写两个配置文件。

  webpack.config.pro.js

  webpack.config.dev.js

  我们先来说一说开发环境,webpack.config.dev.js这个配置文件。这个配置文件里面使用了webpack-dev-server,webpack-dev-serve类似gulp里面的browserSync,可以创建一个前端服务器,具有代码变动监测,自动刷新页面,热替换等功能。这里我把webpack-dev-server的配置文件单独拿出来,写了一个server.js,我们可以通过node server.js来执行这个文件,这个文件会创建一个dev server,并注入webpack.config.dev.js的配置来开启服务器。

  contentBase属性相当于browserSync里面的baseDir,是一个服务器的运行文件目录。

  hot这个属性跟热替换有关,先不说。

var webpack = require(‘webpack‘);
var WebpackDevServer = require(‘webpack-dev-server‘);
var config = require(‘./webpack.config.dev‘);

new WebpackDevServer(webpack(config), {
  contentBase: [‘./client/src‘],
  stats: {
    colors: true
  },
  hot: true,
  historyApiFallback: true,
  headers: {
    ‘Access-Control-Allow-Origin‘: ‘*‘
  }
}).listen(3001, ‘localhost‘, function(err, result) {
  if (err) {
    return console.log(err);
  }
  console.log(‘Listening at http://localhost:3001/‘);
});

  下面我们再看一下webpack.config.dev.js这个文件。

var path = require(‘path‘);
var webpack = require(‘webpack‘);
var autoprefixer = require(‘autoprefixer‘);
var ExtractTextPlugin = require(‘extract-text-webpack-plugin‘);
var source_dir = ‘./client/src‘;

module.exports = {
  cache: true,
  context: __dirname,
  devtool: ‘eval‘,
  entry: {
    chores: [‘webpack-dev-server/client?http://localhost:3001‘],
    business: [
      source_dir + ‘/router‘,
      source_dir + ‘/containers/App‘,
      source_dir + ‘/containers/HomePage‘,
      source_dir + ‘/containers/TaskPage‘,
      source_dir + ‘/containers/SearchPage‘,
      source_dir + ‘/containers/ListPage‘,
      ‘webpack/hot/dev-server‘
    ]
  },
  output: {
    path: ‘/‘,
    filename: ‘scripts/[name].js‘
  },
  module: {
    loaders: [{
      test: /\.js[x]?$/,
      include: /client\/src/,
      loader: ‘babel‘
    }, {
      test: /\.scss$/,
      include: /(client\/src\/containers|client\/src\/components)/,
      loader: ExtractTextPlugin.extract(‘style‘, ‘css?modules&sourceMap&localIdentName=[name]__[local]-[hash:base64:5]!postcss!sass?outputStyle=expanded&sourceMap‘, { publicPath: ‘/‘ })
    }, {
      test: /\.scss$/,
      include: /client\/src\/assets\/styles/,
      loader: ExtractTextPlugin.extract(‘style‘, ‘css!postcss!sass?outputStyle=expanded&sourceMap‘, { publicPath: ‘/‘ })
    }, {
      // inline base64 URLs for <=8k images, direct URLs for the rest
      test: /\.(png|jpe?g|gif|svg)$/,
      loader: ‘url?limit=8192&name=images/[name].[hash:8].[ext]‘
    }, {
      test: /\.(eot|svg|ttf|woff)$/,
      loader: ‘url?limit=8192&name=fonts/[name].[hash:8].[ext]‘
    }]
  },
  postcss: function() {
    return [
      autoprefixer({
        browsers: [‘last 2 versions‘]
      })
    ];
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin(),
    new ExtractTextPlugin(‘styles/main.css‘, {
      allChunks: true
    }),
    new webpack.optimize.DedupePlugin()
  ],
  resolve: {
    extensions: [‘‘, ‘.js‘, ‘.jsx‘]
  }
};

  有点懵逼?没关系,我们一一道来。

  context这个属性是配置文件的上下文环境,我们用node的__dirname就行了。

  devtool是用来配置使用哪种sourceMap的,这里我就不多说了,看官方文档。

  webpack-dev-server/client?http://localhost:3001,引入这个的目的是什么?这个就是启用server自动刷新必须要添加的模块,当然你也可以用--inline模式,但是api方式不支持inline模式,所以必须要把这个模块加在你的所有业务逻辑文件之前。这样你的代码一改,你发现了什么?自动刷新了吧?哈哈哈!

  有了自动刷新,还不满足,我们想要热替换,什么是热替换?就是在不刷新页面的情况下,改变代码就自动改变页面对应的内容。大大节省开发时间(不过这个功能还处于测试阶段)。我这里将它和react hot loader结合使用。

  这里出现第一个坑在新版的hot loader里面,如果你把hot-loader加在babel-loader前面,会出现一个错误,Module build failed:The Webpack loader is now exported separately.如果你的loader是不稳定版的,我建议新建一个.babelrc文件。  

{
  "presets": ["react", "es2015"],
  "plugins": ["react-hot-loader/babel", "transform-runtime"]
}

  然后在文件中作如上配置,除了这个配置,你还需要加上如下配置:

  1. webpack/hot/dev-server,加在你的业务逻辑js文件后面,这里有第二个坑,如果你打包了多个入口,需要在每个入口都加上一个webpack/hot/dev-server,否则不起作用。
  2. 在plugins模块中加入new webpack.HotModuleReplacementPlugin()
  3. 并在之前的那个server.js中将hot属性设为true

  这里我附上一个官方的Troubleshooting链接。

  https://github.com/gaearon/react-hot-loader/blob/master/docs/Troubleshooting.md

 

  很多人知道output是用来指定输出路径的模块,path是用来指定输出文件的目录,publicPath主要是给很多插件提供的路径,比如替换静态资源路径,在开发环境下我们用不到,可以不配置,filename是输出的文件名,这里出现第三个坑,filename不单单指定文件名,也可以加路径,但它会成为path的子路径,output path会是所有插件的上下文输出路径。

  至于配置文件中的其他loader我就不一一讲述了,童鞋们自己去研究吧。

  怎么样,开发环境很简单吧?那么下面我们来配置生产环境,生产环境无非是多了一些编译过程。

  这里有个地方要注意一下,很多用gulp转来用webpack的新手会有个困扰,开发环境下我们编译的文件去哪里了?以前我们用gulp的时候一般会生成一个.tmp临时文件夹,但是webpack好像你找来找去没有找到,在哪里呢?其实webpack给你放到内存里了,你是不会在磁盘中找到这些文件的,如果你想查看这些文件,可以在浏览器中输入像如下的路径。

  http://localhost:3001/webpack-dev-server

这样你就能看到那些编译过的文件了。

  生产环境下我们需要压缩JS,很简单:

new webpack.optimize.UglifyJsPlugin({
      mangle: true,
      sourceMap: true,
      compress: {
        warnings: false
      }
    })

  ExtractTextPlugin用来把sass、less、css文件单独导出,这里不多介绍了,但是有一个坑,注意下面这行代码的publicPath

  ExtractTextPlugin.extract(‘style‘, ‘css!postcss!sass?outputStyle=expanded&sourceMap‘, { publicPath: config.qn_access.origin }),请务必设置,否则在替换图片md5路径时会发生问题,如果不加它会继承css导出的路径,就会出现http://localhost:3001/styles/images/xxx.[md5].png这种情况,会导致无法访问到图片。

  另外autoprefixer,url-loader这些我就不介绍了,gulp能做的,webpack有过之而无不及。

  最后更改文件md5码相同的问题,这里引用一篇文章。

  http://www.cnblogs.com/ihardcoder/p/5623411.html

  看完我相信你就懂了,用上WebpackMd5Hash这个插件,妥妥的解决了问题。

  最后最后,如果你想要替换静态html中的css和js引用路径,可以使用HtmlWebpackPlugin这个插件,还有压缩功能。附上生产环境的配置文件。

var path = require(‘path‘);
var webpack = require(‘webpack‘);
var autoprefixer = require(‘autoprefixer‘);
var ExtractTextPlugin = require(‘extract-text-webpack-plugin‘);
var WebpackMd5Hash = require(‘webpack-md5-hash‘);
var HtmlWebpackPlugin = require(‘html-webpack-plugin‘);
var source_dir = ‘./client/src‘;
var config = require(‘./config‘);

module.exports = {
  cache: true,
  context: __dirname,
  devtool: ‘cheap-module-eval-source-map‘,
  entry: {
    business: [
      source_dir + ‘/router‘,
      source_dir + ‘/containers/App‘,
      source_dir + ‘/containers/HomePage‘,
      source_dir + ‘/containers/TaskPage‘,
      source_dir + ‘/containers/SearchPage‘,
      source_dir + ‘/containers/ListPage‘
    ]
  },
  output: {
    path: ‘client/dist‘,
    publicPath: config.qn_access.origin,
    filename: ‘scripts/[name].[chunkhash:8].js‘
  },
  module: {
    loaders: [{
      test: /\.js[x]?$/,
      include: /client\/src/,
      loader: ‘babel‘
    }, {
      test: /\.scss$/,
      include: /(client\/src\/containers|client\/src\/components)/,
      loader: ExtractTextPlugin.extract(‘style‘, ‘css?modules&sourceMap&localIdentName=[name]__[local]-[hash:base64:5]!postcss!sass?outputStyle=expanded&sourceMap‘, { publicPath: config.qn_access.origin })
    }, {
      test: /\.scss$/,
      include: /client\/src\/assets\/styles/,
      loader: ExtractTextPlugin.extract(‘style‘, ‘css!postcss!sass?outputStyle=expanded&sourceMap‘, { publicPath: config.qn_access.origin })
    }, {
      // inline base64 URLs for <=8k images, direct URLs for the rest
      test: /\.(png|jpe?g|gif|svg)$/,
      loader: ‘url?limit=8192&name=/images/[name].[hash:8].[ext]‘
    }, {
      test: /\.(eot|svg|ttf|woff)$/,
      loader: ‘url?limit=8192&name=/fonts/[name].[hash:8].[ext]‘
    }]
  },
  postcss: function() {
    return [
      autoprefixer({
        browsers: [‘last 2 versions‘]
      })
    ];
  },
  plugins: [
    new ExtractTextPlugin(‘styles/main.[contenthash:8].css‘, {
      allChunks: true
    }),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin({
      mangle: true,
      sourceMap: true,
      compress: {
        warnings: false
      }
    }),
    new WebpackMd5Hash(),
    new HtmlWebpackPlugin({
      title: ‘xxx‘,
      template: ‘client/src/template.html‘
    })
  ],
  resolve: {
    extensions: [‘‘, ‘.js‘, ‘.jsx‘]
  }
};

结语:不得不感慨,JS设计的缺陷反而导致了生态环境的蓬勃发展,总有大神写一些实用插件解决一些语言或者框架的痛点,希望前端圈越来越好,谢谢!

npm scripts + webpack 实践经验(React、Nodejs)