搭建 webpack + React 开发环境
1、初始化环境
2、安装插件
3、配置webpack.config.js 和 webpack.production.config.js 文件
4、遇到的问题及解决办法
5、源文件
一、 初始化 npm 环境
首先保证有 node 和 npm 环境,运行node -v
和npm -v
查看
进入项目目录,运行npm init
按照步骤填写最终生成package.json
文件,所有使用 npm 做依赖管理的项目,根目录下都会有一个这个文件,该文件描述了项目的基本信息以及一些第三方依赖项(插件)。
二、 安装插件
已知我们将使用 webpack 作为构建工具,那么就需要安装相应插件,运行 npm install webpack webpack-dev-server --save-dev
来安装两个插件。
又已知我们将使用 React ,也需要安装相应插件,运行 npm i react react-dom --save
来安装两个插件。其中i
是install
的简写形式。
安装完成之后,查看package.json
可看到多了devDependencies
和dependencies
两项,根目录也多了一个node_modules
文件夹。
--save 和 --save-dev 的区别
npm i
时使用--save
和--save-dev
,可分别将依赖(插件)记录到package.json
中的dependencies
和devDependencies
下面。
dependencies
下记录的是项目在运行时必须依赖的插件,常见的例如react
jquery
等,即及时项目打包好了、上线了,这些也是需要用的,否则程序无法正常执行。
devDependencies
下记录的是项目在开发过程中使用的插件,例如这里我们开发过程中需要使用webpack
打包,而我在工作中使用fis3
打包,但是一旦项目打包发布、上线了之后,webpack
和fis3
就都没有用了。
三、 配置 webpack.config.js
文件格式
webpack.config.js 就是一个普通的 js 文件,符合 commonJS 规范。最后输出一个对象,即module.exports = {...}
输入 & 输出
新建./app/index.jsx
作为入口文件, webpack 支持多个入口文件,可查阅文档。
输出就是一个bundle.js
,js 和 css 都在里面,不过只有在开发环境下才用,发布代码的时候,肯定不能只有这么一个文件。
module
针对不同类型的文件,使用不同的loader
,当然使用之前要安装,例如npm i style-loader css-loader --save-dev
。该项目代码中,我们用到的文件格式有:js/jsx 代码、css/less 代码、图片、字体文件。
less 是 css 的语法糖,可以更高效低冗余的写 css。
在加载 css/less 时用到了postcss
,主要使用autoprefixer
功能,帮助自动加 css3 的浏览器前缀,非常好用。
编译 es6 和 jsx 语法时,用到家喻户晓的babel
,另外还需增加一个.babelrc
的配置文件。
plugins
使用 html 模板(需要npm i html-webpack-plugin --save-dev
),这样可以将输出的文件名(如./bundle.js
)自动注入到 html 中,不用我们自己手写。手写的话,一旦修改就需要改两个地方。
使用热加载和自动打开浏览器插件
devServer
对 webpack-dev-server 的配置
npm start
在 package.json 中,输入以下代码,将这一串命令封装为npm start
,这样就可以运行项目代码了。
"scripts": {
"start": "set NODE_ENV=dev && webpack-dev-server --progress --colors"
}
代码中NODE_ENV=dev
代表当前是开发环境下,这里的"dev"
可被 js 代码中的process.env.NODE_ENV
得到并做一些其他处理。
定义环境全局变量
以下定义,可使得代码通过__DEV__
得到当前是不是开发模式。
new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse((process.env.NODE_ENV == 'dev') || 'false'))
})
四、 配置 webpack.production.config.js
开发环境下,可以不用考虑系统的性能,更多考虑的是如何增加开发效率。而发布系统时,就需要考虑发布之后的系统的性能,包括加载速度、缓存等。下面介绍发布用配置代码和开发用的不一样的地方。
发布到 ./build
文件夹中
path: __dirname + "/build",
vendor
将第三方依赖单独打包。即将 node_modules 文件夹中的代码打包为 vendor.js 将我们自己写的业务代码打包为 app.js。这样有助于缓存,因为在项目维护过程中,第三方依赖不经常变化,而业务代码会经常变化。
md5后缀
为每个打包出来的文件都加md5后缀,例如"/js/[name].[chunkhash:8].js"
,可使文件强缓存。
分目录
打包出来的不同类型的文件,放在不同目录下,例如图片文件将放在img/
目录下
Copyright
自动为打包出来的代码增加 copyright 内容
分模块
new webpack.optimize.OccurenceOrderPlugin(),
压缩代码
使用 Uglify 压缩代码,其中warnings: false
是去掉代码中的 warning
分离 css 和 js 文件
开发环境下,css 代码是放在整个打包出来的那个 bundle.js 文件中的,发布环境下当然不能混淆在一起,使用new ExtractTextPlugin('/css/[name].[chunkhash:8].css'),
将 css 代码分离出来。
npm run build
打开package.json
,查看以下代码。npm start
和npm run build
分别是运行代码和打包项目。另外,"start"、"test"
可以不用run
。
"scripts": {
"start": "set NODE_ENV=dev && webpack-dev-server --progress --colors",
"build": "rd/s/q build && set NODE_ENV=production && webpack --config ./webpack.production.config.js --progress --colors"
},
这两个命令主要有以下区别:
- 前者中默认使用 webpack.config.js 作为配置文件,而后者中强制使用 webpack.production.config.js 作为配置文件
- 前者
NODE_ENV=dev
而后者NODE_ENV=production
,标识不同的环境。而这个"dev" "production"
可以在代码中通过process.env.NODE_ENV
获取。
最小化压缩 React
以下配置可以告诉 React 当前是生产环境,请最小化压缩 js ,即把开发环境中的一些提示、警告、判断通通去掉,直流以下发布之后可用的代码。
new webpack.DefinePlugin({
'process.env':{
'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}
}),
.
五、配置文件遇到的问题
问题 1

解决办法:
修改
resolve: {
extensions: ['','.js', '.jsx']
},
为
resolve: {
extensions: ['.js', '.jsx']
},
问题 2

原因:Webpack 2.1.0-beta23 之后的config里不能直接包含自定义配置项
解决:将postcss
和devServer
替换成以下写法即可
plugins: [
new webpack.LoaderOptionsPlugin({
options: {
postcss: function () {
return [precss, autoprefixer];
},
devServer: {
colors: true, //终端中输出结果为彩色
historyApiFallback: true, //不跳转
inline: true, //实时刷新
hot: true // 使用热加载插件 HotModuleReplacementPlugin
}
}
})
]
问题 3

修改并配置文件如下:
// 方法1
test:/\.(css|less)$/,
use:[
'style-loader',
'css-loader',
{
loader:'postcss-loader',
options:{
plugins:function(){
return [
require('postcss-import')() // 这里
]
}
}
// 方法2 ------------------------------------------------------------
test: /\.(css|less)$/,
exclude: __dirname + '/node_modules/',
use: [
'style-loader',
{
loader: 'css-loader',
options: { importLoaders: 1 }
},
{
loader:'postcss-loader',
options:{ident:'postcss-ident'}
},
'less-loader'
]
问题 4
webpack 图片的处理,图片不显示问题
解决方法:自己设置路径name=./app/static/img/[name].[ext]
,publicPath一般最好不写
test: /\.(png|gif|jpg|jpeg|bmp)$/i,
use: "url-loader?limit=5000&name=./app/static/img/[name].[ext]"
问题 5
webpack 对象扩展运算符报错
原因:babel-preset-es2015组件并不支持该特性,而babel-preset-stage-3能支持该特性
解决办法 :安装 babel-preset-stage-3
插件,并配置presets
"presets": ["react", "es2015","stage-3"],
.
六、 配置文件
webpack.config.js
var path = require('path');
var webpack = require('webpack'); // 自己安装的插件
var HtmlWebpackPlugin = require('html-webpack-plugin'); // 将输出文件自动注入到html页面中,不用手动引入js文件
var OpenBrowserPlugin = require('open-browser-webpack-plugin');
module.exports = {
entry: path.resolve(__dirname, './app/index.jsx'), // 解析成绝对路径
output: {
filename: "bundle.js"
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/, //要解析什么类的文件
use: {
loader: "babel-loader", //需要的插件
options: {
presets: ['es2015', 'react'] //解析什么语言这里是es6 react
}
},
exclude: __dirname + '/node_modules/' //屏蔽不需要处理的文件(文件夹)
},
{
test: /\.(css|less)$/,
exclude: __dirname + '/node_modules/', //屏蔽不需要处理的文件(文件夹)
use: [
'style-loader',
{
loader: 'css-loader',
options: { importLoaders: 1 }
},
{
loader:'postcss-loader',
options:{ident:'postcss-ident'}
},
'less-loader'
]
},
{
test: /\.(png|gif|jpg|jpeg|bmp)$/i,
use: "url-loader?limit=5000&name=./app/static/img/[name].[ext]" // 限制大小5kb
},
{
test: /\.(png|woff|woff2|svg|ttf|eot)($|\?)/i,
use: 'url-loader?limit=5000' // 限制大小小于5k
}
]
},
plugins: [
new webpack.LoaderOptionsPlugin({
options: {
postcss: function () {
return [precss, autoprefixer];
}
}
}),
// html 模板插件
new HtmlWebpackPlugin({
template: __dirname + '/app/index.tmpl.html'
}),
// 热加载 修改页面后自动刷新
new webpack.HotModuleReplacementPlugin(),
// 打开浏览器
new OpenBrowserPlugin({
url: 'http://localhost:8080'
}),
new webpack.DefinePlugin({ // 判断是否是开发环境
__DEV__: JSON.stringify(JSON.parse((process.env.NODE_ENV == 'dev') || 'false'))
})
],
devServer: {
proxy: {
// 凡是 `/api` 开头的 http 请求,都会被代理到 localhost:3000 上
'/api': {
target: 'http://localhost:3000',
secure: false
}
},
contentBase: "./public", //本地服务器所加载的页面所在的目录
// colors: true, //终端中输出结果为彩色
historyApiFallback: true, //不跳转
inline: true, //实时刷新
hot: true // 使用热加载插件 HotModuleReplacementPlugin
}
}
webpack.production.config.js
var pkg = require('./package.json')
var path = require('path')
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
// 发布
module.exports = {
entry: {
app: path.resolve(__dirname, 'app/index.jsx'), // 自己写的代码
// 将 第三方依赖(node_modules中的) 单独打包 项目维护中第三方代码不经常改变
vendor: Object.keys(pkg.dependencies)
},
output: {
path: __dirname + "/build/", // 将文件打包到该文件下
filename: "js/[name].[chunkhash:8].js"
},
resolve:{
extensions:['.js','.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/, //要解析什么类的文件
use: {
loader: "babel-loader", //需要的插件
options: {
presets: ['es2015', 'react'] //解析什么语言这里是es6 react
}
},
exclude: __dirname + '/node_modules/' //屏蔽不需要处理的文件(文件夹)
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract([ 'css-loader', 'postcss-loader' ])
},
{
test: /\.less$/i,
use: ExtractTextPlugin.extract([ 'css-loader', 'less-loader' ])
},
{
test: /\.(png|gif|jpg|jpeg|bmp)$/i,
use: "url-loader?limit=5000&name=./app/static/img/[name].[ext]" // 限制大小5kb
},
{
test: /\.(png|woff|woff2|svg|ttf|eot)($|\?)/i,
use: 'url-loader?limit=5000' // 限制大小小于5k
}
]
},
plugins: [
new webpack.LoaderOptionsPlugin({
options: {
postcss: function () {
return [precss, autoprefixer];
}
}
}),
// webpack 内置的 banner-plugin 可以写标注
new webpack.BannerPlugin("Copyright"),
// html 模板插件
new HtmlWebpackPlugin({
template: __dirname + '/app/index.tmpl.html'
}),
// 定义为生产环境,编译 React 时压缩到最小
new webpack.DefinePlugin({
'process.env':{
'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}
}),
// 为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
// new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false // 把搜索的警告去掉
}
}),
// 分离CSS和JS文件
new ExtractTextPlugin('/css/[name].[chunkhash:8].css'),
// 提供公共代码
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: '/js/[name].[chunkhash:8].js'
}),
// 可在业务 js 代码中使用 __DEV__ 判断是否是dev模式(dev模式下可以提示错误、测试报告等, production模式不提示)
new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse((process.env.NODE_ENV == 'dev') || 'false'))
})
],
devServer: {
proxy: {
// 凡是 `/api` 开头的 http 请求,都会被代理到 localhost:3000 上
'/api': {
target: 'http://localhost:3000',
secure: false
}
},
contentBase: "./public", //本地服务器所加载的页面所在的目录
// colors: true, //终端中输出结果为彩色
historyApiFallback: true, //不跳转
inline: true, //实时刷新
hot: true // 使用热加载插件 HotModuleReplacementPlugin
}
}