构建一个小项目——FlyBird,学习webpack和react。
(本文成文于2017/2/25)
从webpack开始
本篇从零开始,详细记录webpack的各个方面。
文章中将会放入很多链接以便扩展,我也会归纳总结,不读扩展不会影响到对本文的理解,但是有时间还是看看吧。
声明:
在阅读本文列出的链接文章时,若遇到与本文不同的,因为文章的时效性问题——
请以本文为标准
当前时间2017/2/26 在此之后出现的文章,读者请注意对比,自行判断
- 核心资料:
webpack2官网doc中文版——小书 后文中的简称—小书—专指此链接
此文档非常详细,最近一次修订时间为——2017 年 2 月十几号
目前阅读起来,尽管放心。本文为教程,小书为手册,二合一,棒棒哒。(您可以亲自去看看他是啥时候又更新了,如果依然比较新,或许很多问题,可以在里边找到答案)
- 其他较完整的小书型资料:
webpack_github库doc
webpack.js.org(小书的英文原文)
webpack for React英文
webpack傻瓜教程(较老)
webpack中文教程——赵达(老书)
后边的虽然是老书,但是,多条资料,就多一条路,不是么?
- 有助于学习理解的代码库
在文章结尾
开始
最近在学习react,难免看到网上各种webpack+react的文章,发现有些很全面的资料,内容却有些过时(比如有个gitbook的书,是在react还没有分离react react-dom的时候写的),而有的资料则虽然挺新,但是往往只谈一个方面。
种种原因,我决定,结合官网,记录下webpack的各个方面,系统学习一下。
从零开始构建小项目-FlyBird(源代码可在文章结尾处找到)
这是原始数据目录(原生写的)
Paste_Image.png可以看到,整个项目有一些js文件,一堆img文件,一个css文件。将来我们也要一步步亲自实现他们,这个目录展示了整个项目大概需要些什么。让我们使用webpack构建工作流来管理未来我们将要写的代码吧。
webpack安装与配置
1.npm
创建一个文件夹,并在文件夹下打开命令行
Paste_Image.png我们需要node-npm来安装和运行webpack,关于node和npm不懂得同学请自行百度。
拥有node-npm后
在当前文件夹初始化npm的package.json文件
npm init
相关问题随便填。这会创建 package.json文件,不用担心问题填错,你可以之后修改它。
这句命令就表示,我们把当前文件夹,初始化为一个npm包,它处于npm的管理之下。
我们可以通过npm下载其他人的包,构成了自己包的依赖;当我们完成了我们的包,也可以发布它,让别人下载。最主要的是使用npm来管理依赖。
package.json文件用来配置当前包,配置文件含有很多属性,反映着包的不同信息,后文中,遇到一个介绍一个,而不做全面介绍。
(详情请移步package.json属性详解)
当init后,我们有如下package.json
//package.json
{
"name": "mydemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
- name,version是必须的,也是最重要的,他们表示包的名称和版本,构成了包的唯一标示。假如我们只是用npm来管理依赖,他们自然不重要,但是如果我们在制作一个供他人使用的工具包,便必须正确的书写他们,以便他人查找和使用。具体的规则请查看之前的网站。
- description main author license
这些属性,仅在需要制作工具包时关注。description author license 顾名思义,main则表示当别人引用(require)你的工具包时,入口文件在哪。 - scripts则与实际开发过程有关,是我们会经常用到的。
通过它,我们可以定义npm脚本,比如
//只看,不用写后边需要动手写,我会说
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server --devtool eval --progress --colors --hot --content-base build"
}
当我们在命令行npm run build
时,就等于webpack
,原理很简单,在执行npm run
命令时,会新创建一个Shell,并将当前目录的/node_modules/.bin/
加入到PATH
变量(环境变量),之后运行脚本命令,结束后,将PATH
恢复原样。
详情请移步:
阮一峰——npm scripts使用指南
npm-scripts官方文档(英文)
好吧,其实太高级的并木有卵用,目前,只知道可以懒省事就行啦(o)/~,比如npm run dev
,如果每次都打一长串,会疯的。
2.webpack
是时候安装webpack了!不过在安装之前,还要介绍一些概念。
概念——开发与发布
一个项目通常都会有,开发,发布这两种状态,也就是自己瞎捣鼓,和放网上让别人用。不管是哪种状态,我们的项目都可能会依赖别人的包。那么自然而然,因为状态的不同,依赖的包也不太一样。比如,在开发阶段,往往需要进行测试,看看能不能跑通,而测试工具显然在发布时是不必要的。
为了分别管理,npm在package.json提供了这样的字段
- devDependencies 声明—仅开发依赖
- dependencies 依赖包
在下载别人的包时,如果只期望下载一个发布的可运行版本,而不希望对此包进行任何开发,可以利用npm install --production
仅下载dependencies字段中的依赖。
好吧,对于我们的项目并没有什么卵用,因为我们将使用webpack管理整个工作流程,npm只是用来下载东西(囧),我只是说明一下。
开发与部署
开发到一定阶段需要发布一个版本,我们往往需要一个文件夹来保存整合后的项目。这个过程,就叫做部署"deploy"。这也是webpack的工作,会用到一个和开发阶段不同的webpack配置文件,它只是将输出目录换成了另一个而已。
在我们的小项目进入到这个阶段后,再细说。
安装webpack
npm install webpack --save-dev
这将在本地(当前文件夹下)安装webpack并在开发依赖字段(devDependencies )中保存信息。
webpack显然只需要在开发阶段用到
如果想要运行它,进入node_modules/.bin
,并运行它webpack
。
当然,我们也可以在上文提到的package.json中的scripts字段中配上
//动手写
"scripts": {
"build": "webpack" //由于scripts将node_modules/.bin加入到环境变量PATH中,所以脚本Shell可以搜索到webpack指令,`npm run a`等价的`webpack`也就可以运行了。
},
注意,不推荐全局安装 webpack
npm i webpack g
。这会锁定 webpack 到指定版本,并且在使用不同的 webpack 版本的项目中可能会导致构建失败。
webpack的使命
从上边目录图中也可以看到,我们需要用webpack管理很多东西,依赖包,自己写的jsx,css,各种各样的图片,也许还有字体。
为了性能,我们需要根据依赖关系,对各种jsx,css,img进行压缩整合为数量跟少的几个文件。
为了开发方便,我们需要浏览器自动刷新,sass/less自动转换的功能等等。
这些,就是webpack的使命,让我们的开发更高效。
构建目录
可以简单的划分为来源——去处,如图
目录- app文件夹,就所有我们手写的文件放的地方
- build文件夹,则是经过webpack打包,自动生成的文件的去处。
在build文件夹下,新建index.html用来表示我们的索引页
它长这样,其中的div用来给做一些页面修改什么的用
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>FlyBird</title>
</head>
<body>
<div id="div1"></div>
<script src="bundle.js"></script>
</body>
</html>
对于部署文件夹来说,一般是这样
部署- dist文件夹,用来保存我们的发布版。
所以,最终,在app中写东西,打包到build中调试、看效果,不错的话,发布到dist文件夹里。
配置webpack
架子已经搭好了,现在,我们需要控制webpack的各种行为,添加各种功能。
可以有三种途径
- cli——即命令行形式,一般都会动过package.json中写入scripts字段的形式
- 配置文件
webpack.config.js
文件中写入字段,webpack在启动时会读取它,并依据其工作 - node api——其实配置文件也算node api,更广义的来讲,node api是一套配置文件的生成系统,根据不同的输入(从cli传参数
--a=b
),实现不同的配置。
对于第三种,本文不做过多介绍,在文章最后,我会贴出一位前辈的node api模板地址,有兴趣的同学可以移步文章结尾。
注意:当使用node api同时又使用了webpack.config.js时,webpack.config.js将不会生效
我们通过配置文件,此时你的目录应该是这样(新建文件)
配置文件因为我们会往两个地方打包,那么自然,得俩配置文件了。
-
webpack.config.js
目标build -
webpack.production.config.js
目标dist
接下来,通过webpack.config.js的配置来详细介绍配置属性(到部署时候再说后production配置)
进行如下配置
//动手写
// webpack.config.js
var path = require("path");
module.exports = {
entry: path.join(__dirname, '/app/main.js'),
output: {
path: path.join(__dirname, '/build'),//打包后的文件存放的地方
filename: "bundle.js"//打包后输出文件的文件名
}
};
__dirname 是当前运行的js所在的目录
模块的依赖书写方式:
方式取决于模块系统,上文中很明显是commonJS的方式。
webpack支持最新的模块系统——es6的模块系统(ES6 module import),
但是,注意:这并不是说它支持es6。
也就是说,我们可以直接使用如import/export
语句来导入模块,但是,如果我们想要打包含有其他es6语法的模块时,依然需要babel转换器
关于模块系统这里有大略的介绍——webpack中文指南——赵达(模块系统)
一个一个来说
基本配置属性介绍(一)——entry
1.基本概念
表示模块的来源,入口,起点。它的值可以是
- 字符串
entry: '某模块'
表示一个单一模块作为起点(当然,单一入口也可以用后边两种写),把这个模块需要的东西打包成一堆 - 数组
entry: ['模块1', '模块2']
模块1与模块2互相之间并没有依赖,但是我们还想把他们打包在一起,此时就用数组值的方式,webpack从左到右把各个模块及他们的依赖打包在一起([第一堆,第二堆]),然后从左到右首尾相连的拼接成一个文件。最终也是打包成一堆。 - 对象
//只看
entry: {
page1: "./page1",
page2: ["./entry1", "./entry2"]
}
这将会打包成两堆,每一堆都有一个[name]属性,值为对应entry中的属性名。
//只看
output: {
// Make sure to use [name] or [id] in output.filename
// when using multiple entry points
path: '/build',
filename: "[name].bundle.js",
chunkFilename: "[id].bundle.js"
}
在filename中使用[name]来生成对应打包堆特殊的名字。
- 混合使用,不在赘述
entry的官方文档
2.各种问题
-
entry值的写法问题
上网大眼一看,有三种
entry: path.resolve(__dirname, 'app');
entry: path.join(__dirname, 'app');
entry: __dirname + '/app';
在linux,mac环境下这三种是一样的,而在windows环境下,最后一种是错误的
测试图
这是因为,node的path模块的方法,在解析路径时候,会使用当前平台的路径分隔符,windows的是\
。
如果我们直接使用+
号拼接,自然发生错误。
-
path.resolve与path.join
join方法仅仅进行路径拼接
resolve方法则会做一些解析工作,它会将参数从右到左拼起来,直到遇到一个绝对路径。path的node官网文档总结一下路径符号
'.'表示当前目录
`..`表示上一级目录
`/`表示路径起点——绝对路径的标志,通常为当前运行脚本所处的位置。
所以在使用resolve的时候要注意。
- context配置——entry的根目录
//只看
{
context: path.join(__dirname, 'app'),
entry: "entry",
}
我们也可以通过context来定义entry的根目录,这也同时定义了后边output.pathinfo
、loader项下的reslove(v2版本新改动,可在小书中查看)
的根目录。
如果我们不声明context字段,默认为process.cwd()
cwd()
是当前执行node命令时候的文件夹地址
__dirname
是被执行的js 文件的地址
-
小结语
对于我们的FlyBird项目来说,显然仅仅是一个单页面应用,只需要一个入口,所以entry设置极其简单
//没啥变化
module.exports = {
entry: path.join(__dirname, '/app/main.js'),
output: {
path: path.join(__dirname, '/build'),
filename: "bundle.js"
}
};
基本配置属性介绍(二)output
1.基本概念
output规定了如何将打包后的一堆堆的东西,写在磁盘里。
它的内容真的非常多,我会挑出之后要用的,具体说说;想了解具体的,请看小书
2.各种问题
-
output.filename多个chunk输出?
也就是上边entry是对象情况,我们必须保证输出的名字唯一性。每个chunk都有一些属性来帮助我们达到目的-
[name]
最简单,就是在entry里边的属性名 -
[hash]
is replaced by the hash of the compilation -
[chunkhash]
is replaced by the hash of the chunk
后俩不明白?他们牵扯到了缓存,我会在下文做简述,并且之后会专门写一篇关于webpack缓存的总结
了解更多,请移步这里——hash-chunkhash的理解,区别。(这篇文章从概念上讲解了hash和chunkhash,在下文的缓存简述里,我会做进一步的说明)
2017/2/25 21:02更新——照现在这进度,想写缓存总结不知到猴年马月了,您还是看看下边的文章吧(下文简述已经写好了)
-
-
filename与chunkFIlename区别?
请移步——filename与chunkFIlename区别 -
output.path与output.publicPath?
output.path值为输出目录的绝对路径,也可用[hash]
output.publicPath项则被许多Webpack的插件用于在生产模式下更新内嵌到css、html文件里的url值,在热加载模块应当关注它,必须通过这个属性来告诉热加载模块去哪加载
关于他俩的区别的具体解释,请移步path与publicPath(往下翻,第4条,当然前边也可以看看)
3.小结语
好了,关于output常用就这几个。忍不住吐槽下,它属性真的太多了,还大部分不知道有啥用(想了解更多,去看小书哦)
到此我们已经完成了最简单的webpack.config.js的配置,通过这个配置webpack可以将main.js打包成bundle.js
下面让我们来试一试是否有效
~~
※配置webpack-dev-server这个服务器工具
webpack-dev-server可以让浏览器实时刷新,显示我们对文件的改动,不如趁着验证main.js和bundle.js的过程,也一并尝试一下。
webpack-dev-server是一个小型的node.js Express服务器,为webpack打包生成的资源文件提供Web服务。webpack-dev-server发送关于编译状态的消息到客户端,客户端根据消息作出响应。
——想看更多可以浏览这个webpack-dev-server解读
~
如果你不希望使用webpack-dev-server来启动服务,(要么是你有服务器了,要么是你想知道不用它怎么启动服务),详情请参考
小书——开发(如果你有服务器,请翻到此链接最后,查看webpack-dev-middleware,本文不会对这种情况做过多讨论)
1.安装
这个功能是一个独立的模块实现的,所以我们首先要安装这个模块
npm install --save-dev webpack-dev-server
默认情况下,它将在当前文件夹下启动一个websocket服务,端口号为8080
两种方法配置服务(选择其一)
1.配置文件 ——Node API
2.cmd指令(推荐)
- 配置文件(Node API)
// webpack.config.js加入
//只看
devServer: {
port: 8080 //设置监听端口(默认的就是8080)
contentBase: "./build",//本地服务器所加载的页面所在的目录
colors: true,//终端中输出结果为彩色
historyApiFallback: true,//不跳转,用于开发单页面应用,依赖于HTML5 history API 设置为true点击链接还是指向index.html
}
如果你需要以server.js的形式写出相关配置,也不是什么难事
const WebpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');
const config = require('./webpack.config.js');
const path = require('path');
const compiler = webpack(config);
const server = new WebpackDevServer(compiler, {
contentBase: 'www',
hot: true,
filename: 'bundle.js',
publicPath: '/',
stats: {
colors: true,
},
});
server.listen(8080, 'localhost', function() {});
关于如何以server.js方式定义配置,将会在文章最后给出模板链接,已经有前辈做好了一切(伸手就有,感觉真好!上边代码里不认识的不要紧,)
- cmd指令
webpack-dev-server --devtool eval-source-map --progress --colors --content-base ./build
// --代表一个指令,与上边各个属性对应
// 此处展示的是经常会在网上看到的写法,并非我的写法
--devtool eval-source-map
与webpack-dev-server没关系,先不谈,会在调试模块详细说明
--progress --colors
:前着表示显示打包过程,后者是给显示出来的进度加点颜色(一篇绿,一点红,手动滑稽~)。
--content-base
:设置目录
注意:
有一些在前边出现的属性,并没有被对应的写在cmd指令中。事实上,大多数配置都有两种写法,比如:
historyApiFallback: true
对应--history-api-fallback
port:8080
对应--port 8080
这些并不是我介绍的重点,如果你想要了解更多细节,请移步
小书api章节
如果你希望看到一些具体例子,请移步
webpack-github的examples文件夹
如果我们使用cmd指令,每次都打那么长,绝对要疯,所以~~~
//动手写
// package.json
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server --devtool eval-source-map --progress --colors --content-base ./build"
},
目前,最简单的配置已经完备。我们有了一个服务器,它的端口号默认是8080
让我们写一些具体的东西,测试一下它的效果
- 首先在app文件夹下新建main.js
它长这样
// 新建main.js
document.write('我');
里边的内容自然你随便写。
现在启动服务器
npm run dev
就可以在浏览器访问通过localhost:8080
访问,此时,我们在本地做出更改,然后浏览器将自动刷新,即可看到效果。
但是,如果观察页面:
热加载引入我们的页面整个都被刷新了,这显然是不高效的,为什么?
假如工程非常的庞大(实际开发中,往往都是‘非常庞大’),页面像我这篇文章一样,很长,,,修改一个很小的地方,如果页面整个刷新,第一,需要等待页面刷新完,很难过,第二,刷新出来,咱还得滚动半天到咱们修改的地方看效果。等等。都是不高效的。
所以我们需要另一个功能
- 热加载
然而实现这个功能的过程中会遇到很多问题,接下来我将通过——问与答逐个说明
问题一:自动刷新功能的两种模式?以及如何配置?
这个是个用来承上启下的问题,首先,我们要更深入的了解一下自动刷新功能。
1.两种模式
webpack-dev-server自带就有自动刷新功能,而且它是有两种启动模式的
- iframe模式
在iframe模式下:页面是嵌套在一个iframe下的,在代码发生改动的时候,这个iframe会重新加载
关于iframe是什么请自行百度,这不是我的重点。
- inline模式
在inline模式下:一个小型的webpack-dev-server客户端会作为入口文件打包,这个客户端会在后端代码改变的时候刷新页面
什么叫作为入口文件打包?它其实类似这样,请看
entry: [
'webpack-dev-server/client?http://0.0.0.0:9090',//资源服务器地址
'webpack/hot/only-dev-server',
path.resolve(__dirname, "app")
]
// 数组第一项就是那个小型服务器
这样的写法,网上webpack文章上经常见,联想一下上文介绍的entry的数组值的意思。没错,inline模式就是做了这样的事情。
关于数组第二项:它是一个api,然而它只有node api的写法,没有cli的写法。在浏览小书的api章节,你会发现一些类似的。
hotOnly: true
那么我们还可以像上边那样写,也就是手动的启动了这个服务。(揪住此功能的实体,把他强制打包进来)。
或许你在网上还见过另一种方式,通过index.html在bundle.js之前插入它。其实道理是一样的。
2.配置
webpack-dev-server默认开启inline模式,请看:
实时刷新测试 实时刷新测试2.png但是,难道不能启动iframe模式了么?非也,请看
iframe那么,我们并没有启动iframe模式啊?这与上边的hotOnly那一块说的一个意思,是我们手动启动了服务
我们可以关闭默认启动的inline模式:--no-inline
命令
但是想要看到iframe模式,依然需要再浏览器中端口号后加上/webpack-dev-server/
注意:在网上,你会看到各种各样的奇怪说法,请依据你的demo结果,结合本文理解。(包括本文引用的文章里,好多都说的很奇葩,就连小书也没有说清楚这一块)
最后,总结一下:
实时刷新功能,根本不用我们管,webpack-dev-server已经做好了一切。
问题二:明明设置正确,却不会自动刷新,为什么?
1.编辑器
一些文本编辑器有“safe write”(安全写入)功能,并且默认启用。因此,保存文件后并不总是会导致 webpack 重新编译。
每个编辑器都有不同的方式来禁用这一功能,以下是一些最常见的:
- Sublime Text 3 - 在用户设置(preference)中增加 "atomic_save": false。
- IntelliJ - 在设置中查找 “safe write”并且禁用它。
- Vim - 在设置中增加 :set backupcopy=yes。
- WebStorm - 在 Preferences > Appearance & Behavior > System Settings 中取消选中 Use "safe write"。
摘录自————小书—开发
2.另一种可能——缓存
缓存简述
博主遇到过无法自动刷新的问题,找不到原因,第二天莫名其妙好了。
当我想要重现错误时,它死活不会错,关于是不是缓存导致,没有办法验证。
我们都知道浏览器会缓存请求的文件,当我们再次请求时,首先从缓存列表查找,没有再去请求。这会提高性能。
很明显,我们的bundle.js并不是从磁盘缓存中来的(细心的同学会说,哇,bundle.js为什么那么大?!这牵扯甚广,又是一篇文章的量啊)
缓存2
点开bundle.js查看他的http请求头,
cache-control:max-age=0
此项用来表示不缓存。
如果缓存,缓存多久?
-
max-age
指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应(不超过这个秒数的,都用缓存里的) -
Expires
表示存在时间,允许客户端在这个时间之前不去检查(发请求),等同max-age的
效果。但是如果同时存在,则被Cache-Control的max-age覆盖。
关于缓存概念的更多信息请移步————
HTTP头的Expires与Cache-control
前端缓存策略与基于Webpack的静态资源版本管理
缓存是解决性能问题的一大帮手,我会专门写一个文章来分析它(不知到何时才能成文),此处不再多说。
到目前为止,并没有再发生奇怪的错误。
问题三热替换Hot Module Replacement、react-hot-loader与react-transform
1.基本概念
什么是热替换?就是局部刷新,这样,提高开发效率,节约时间。前边我们使用inline方式,每次都会刷新整个页面,而iframe模式会刷新整个iframe标签,显然不是我们想要的高效
按照大部分网上教的,弄的我晕头转向,到最后还做不出来一个热替换效果。深受其害,,,无力吐槽
webpack-dev-server自带的Hot Module Replacement模块用来实现热替换功能。
两种方式开启它:
- cmd
- nodeapi
不管我们用那种方式,只要开启,webpack就会向我们的模块暴露module.hot
因此,我们可以使用 module.hot
钩子函数为特定资源启用 HMR。这里最重要的 API 是 module.hot.accept,它指定如何处理对特定依赖的更改。
所以,我们一步步自己测试一下
// 在main.js的目录下创建component.js
// component.js
var oDiv = document.querySelector("#div1");
oDiv.textContent = "我是布雷布雷,你好啊";
// main.js
require("./component.js");
if(module.hot) {
module.hot.accept();
}
我们模拟了一个组件,并在main.js中引用了它。
Paste_Image.png接下来,必须将webpack.config.js中,output下的publicPath与devServer下的publicPath设置为一样
// webpack.config.js
···
output: {
···
publicPath: '/',
···
}
···
devServer: {
···
publicPath: '/',
···
}
···
下面关于两种配置方式
-
cmd
hot模块
--hot
设置它之后,启动服务,在浏览器可以看到:
- HMR是hot模块产生
- WDS是webpack-dev-server模块产生
-
配置文件
Node.js API方式需要做两个配置:- 把
new webpack.HotModuleReplacementPlugin()
加入到webpack配置文件的plugins
项; - 把
hot:true
加入到webpack-dev-server的配置项(devServer)里面。
两项都必须加大家可以自行验证一下,博主已经一一验证了。
- 把
-
关于
webpack/hot/only-dev-server
前文也介绍了,虽然不加没错,但是还是加上比较好。 -
在研究热加载的过程中,有一些很让我生气的事
热加载错误.png下边的写法是错误的!!!!!!
将webpack/hot/dev-server
添加到entry中
把它改为
webpack/hot/dev-server.js
名字写全,就可以了。还不清楚为什么报错,有知道的小伙伴,请指教一二!
说到底还是命令行方式最简单——像下边这样写就好
//你的应该也长这样
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server --devtool eval-source-map --progress --colors --hot --content-base ./build "
},
其他配置选项
--quiet 控制台中不输出打包的信息
--compress 开启gzip压缩
--progress 显示打包的进度
更多配置信息——官网webpack-dev-server-cli
~~
2.三个模块
以上配置好后,我们已经可以热加载实时刷新了,但是我们的项目FlyBird(我都快把他给忘了)想要用到react,组件化的react给热加载带来了一些麻烦。
react-hot-loader
来解决问题
我们都知道react组件是状态机,有state对象来表示状态,假如因为一些小改动,导致要渲染整个组件,此时现有状态就会丢失。react-hot-loader
就是来解决这个问题的。所以它是必须的。
react-transform
被弃用
react-transform
也是用来解决这个问题,不过它已经老早就停止维护了,所以不要使用它,咱们按官网上的,都是使用react-hot-loader
由于react使用的是jsx+es6语法,所以我们不能仅仅只是打个包完事,还要在打包过程中使用babel对其进行编码转换。
总结一下:
- 使用
react-hot-loader
- 使用
babel
(babel不懂的,请自行百度教程)
不再解释,直接上代码
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-2
因为,默认安装的还是v1版本的react-hot-loader,我们要使用的是最新的版本,一定要带上版本号。
npm install --save-dev react-hot-loader@3.0.0-beta.6
npm install --save react react-dom
新建.babelrc
并配置
// 新建.babelrc
{
"presets": [
["es2015", {"modules": false}],
// webpack现在已经支持原生的import语句了, 并且将其运用在tree-shaking特性上
"stage-2",
// 规定JS运用的语言规范层级
// Stage 2 是 "草案", 4 是 "已完成", 0 is "稻草人(strawman)"。
// 详情查看 https://tc39.github.io/process-document/
"react"
// 转译React组件为JS代码
],
"plugins": [
"react-hot-loader/babel"
// 开启react代码的模块热替换(HMR)
]
}
配置webpack.config.js,因为我们已经使用babel转义es6所以,尽情使用吧
const { resolve } = require('path');
const webpack = require('webpack');
module.exports = {
context: __dirname,
entry: [
'react-hot-loader/patch',
'webpack/hot/only-dev-server',
'./app/main.js'
],
output: {
path: resolve(__dirname, 'build'),//打包后的文件存放的地方
filename: "bundle.js",//打包后输出文件的文件名
publicPath: "/"
},
devServer: {
contentBase: resolve(__dirname, 'build'),
hot: true,
publicPath:'/'
},
module: {
rules: [
{
test: /\.jsx?$/,
use: [
'babel-loader',
],
exclude: /node_modules/
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
],
devtool: "cheap-eval-source-map",
};
编写main.js,写一段react测试一下,也是好的
import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import Cpt from './component';
const render = (Component) => {
ReactDOM.render(
<AppContainer>
<Component />
</AppContainer>,
document.getElementById('div1')
)
};
render(Cpt);
if(module.hot) {
module.hot.accept('./component', () => {
render(Cpt)
});
}
最关键的就是module.hot,一定要写,它用来告诉webpack怎么去热替换
这是我们的组件component.js,它长这样
import React from 'react';
const Cpt = () => (
<div>
<h1>我是盖世英雄!</h1>
</div>
);
export default Cpt;
react热替换成功!
很多朋友还在使用v1版本的react-hot-loader,网上很多教程也都是v1版本的,小伙伴们在看资料时候一定要注意区别。
如果你想了解更多——
react-hot-loader的github可以在这里了解到api,与以前的区别,还可以看到很多模板。
这里是一些区别的讨论
都是英文的,小伙伴要有点耐心读哦
这是react-hot-loader的网站
结语
1.一些话
戛然而止,,,好吧,是我的错,我小小的解释一下。
写这篇文章,完全超出了我的预计,我以为两天就能写完,然而整整用了五天,从早上8点坐下来,一直到晚上11点,有两天午饭都忘吃了。
中间遇到了很多困难,,比如研究缓存那一块,因为莫名出错又莫名其妙好了,我想还原错误,猜测是不是缓存问题,就去清缓存,浏览器自带的缓存清理不给力,我就手动把我谷歌下的user data文件夹给删了。。。删完之后才意识到,自己书签还没备份,,,唉,上百个书签,都是心血啊,又花了点钱买数据恢复,也没恢复过来,又白赔进去几百块钱。
类似种种吧,这会儿真的有点累,昨天熬夜到4点多,想着能搞定,谁知道还是今天才弄完。
当然,其实这些都不是最关键的,累了,休息休息就好。
最主要的是,博主还是学生,马上要去找工作了,,,然而,我还没有开始复习。。。僵硬。。。所以,必须暂时停下来了。
2.关于这个项目
FlyBird,,,好吧,我都快把它给忘了。其实按计划,今天是能够做完的,因为只是一个简单的重构,就像todoMVC一样。
忙完这一段我依然会着手完成它,就像做,下面对接下来需要讨论的问题汇总一下,以备以后使用
未解决的问题:
缓存策略
| 插件、loader
打包策略
| 插件、loader、同步异步加载
生产环境构建
| 插件、loader
| 使用node api构建配置文件生成系统
懒加载
| 插件、loader
测试功能
| 插件、loader
兼容问题
| 插件、loader
v1到v2版本的改动
| 插件、loader、api接口、书写方式等等一大堆
关于webpack,小书上其实很全面了,而且比较新,大家可以放心去查阅,当然,也要关注官网的最新的消息。
react部分,其实没有什么好说的,因为网上的教材也比较全了,当然,这不是我以后偷懒的理由。
我会回来完善这篇文章的,让它成为一个完整的,友好的引导。这是我对自己的承诺。
3.放在最后的资料
文章小例子github库——
FlyBird这个从哪来的?感谢这篇文章带给我的一些冲动
JavaScript实现Fly Bird小游戏
有一些很棒的github库,也放上来
- webpack-dev-server的github,可以在examples文件夹里找到例子
- create react app 两万多的星。。。
- webpack-hmr-3-ways 名字已经说明了一切,很不错
- react-learn
- react-redux-webpack模板
- react-hot-loader的github
- react-static-boilerplate
- react-hot-boilerplate
一些我觉得应该仔细看看的文章:
网友评论
webpack中文文档:http://www.jqhtml.com/7626.html
webpack的话,怎么写的高级一点(就跟各种cli里边的那种),webpack3 也要了解了解
项目的话,一个管理系统,目前写了一半吧,技术栈是react + redux + ant design,主要是权限管理,数据列表展示和操作,过滤器。
不敢保证啥时候能搞定,,,工作比较忙。
注意,不推荐全局安装 webpacknpm i webpack g。这会锁定 webpack 到指定版本,并且在使用不同的 webpack 版本的项目中可能会导致构建失败。还有这句话怎么理解?
你的意思是webpack不需要全局安装,但是我看教程上都说是先全局安装,后局部安装
一般找不到依赖包就再安一遍。
webpack版本不同,一些配置的写法也不一样,比如1.x跟2.x就有很大区别。假如说我们全局安装,那么的package.json就没有依赖的记录。别人用的时候,就不知道webpack的版本。如果他们的电脑也全局安装了webpack,但是与我们的版本不一样,就有可能出现问题。
所以,我们要把webpack依赖写清楚.
这个就是项目与本地环境的解耦,保证不会错
"dev": "webpack-dev-server --devtool eval-source-map --progress --colors --hot --open --content-base ./build "
要不是不不会出效果的