首页 > 代码库 > 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"]
}
然后在文件中作如上配置,除了这个配置,你还需要加上如下配置:
- webpack/hot/dev-server,加在你的业务逻辑js文件后面,这里有第二个坑,如果你打包了多个入口,需要在每个入口都加上一个webpack/hot/dev-server,否则不起作用。
- 在plugins模块中加入new webpack.HotModuleReplacementPlugin()
- 并在之前的那个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)