webpack-react 之 webpack 篇(上)

2,879 阅读27分钟
原文链接: www.jianshu.com

构建一个小项目——FlyBird,学习webpack和react。
(本文成文于2017/2/25)

从webpack开始
本篇从零开始,详细记录webpack的各个方面。
文章中将会放入很多链接以便扩展,我也会归纳总结,不读扩展不会影响到对本文的理解,但是有时间还是看看吧。

声明:

在阅读本文列出的链接文章时,若遇到与本文不同的,因为文章的时效性问题——

请以本文为标准

当前时间2017/2/26 在此之后出现的文章,读者请注意对比,自行判断

开始

最近在学习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`也就可以运行了。
  },

注意,不推荐全局安装 webpacknpm 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.pathinfoloader项下的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命令


iframe2

但是想要看到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.另一种可能——缓存

缓存简述

博主遇到过无法自动刷新的问题,找不到原因,第二天莫名其妙好了。
当我想要重现错误时,它死活不会错,关于是不是缓存导致,没有办法验证。

我们都知道浏览器会缓存请求的文件,当我们再次请求时,首先从缓存列表查找,没有再去请求。这会提高性能。


缓存1.png


很明显,我们的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)里面。
      两项都必须加大家可以自行验证一下,博主已经一一验证了。

Paste_Image.png

热加载nodeapi
  • 关于webpack/hot/only-dev-server前文也介绍了,虽然不加没错,但是还是加上比较好。

  • 在研究热加载的过程中,有一些很让我生气的事

    下边的写法是错误的!!!!!!
    webpack/hot/dev-server添加到entry中


    热加载错误.png


    把它改为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库,也放上来

一些我觉得应该仔细看看的文章:

未完待续,敬请期待