webpack-react 之 webpack 篇(上)

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

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

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

声明:

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

请以本文为标准

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

开始

最近在学习react,难免看到网上各种webpack+react的文章,发现有些很全面的资料,内容却有些过时(比如有个gitbook的书,是在react还没有分离react react-dom的时候写的),而有的资料则虽然挺新,但是往往只谈一个方面。

种种原因,我决定,结合官网,记录下webpack的各个方面,系统学习一下。

从零开始构建小项目-FlyBird(源代码可在文章结尾处找到)

这是原始数据目录(原生写的)

6b8f7edf8ee009a151a8566818db72f95af9af17

Paste_Image.png

可以看到,整个项目有一些js文件,一堆img文件,一个css文件。将来我们也要一步步亲自实现他们,这个目录展示了整个项目大概需要些什么。让我们使用webpack构建工作流来管理未来我们将要写的代码吧。

webpack安装与配置

1.npm

创建一个文件夹,并在文件夹下打开命令行

292e812e3e8588cdebc8e91ff6ec3cc2e8e0ae0b

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"
}

好吧,其实太高级的并木有卵用,目前,只知道可以懒省事就行啦(^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的使命,让我们的开发更高效。

构建目录

可以简单的划分为来源——去处,如图

bb9ab633ac15176e83e5be4cdf6917b3b451743f
目录
  • 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>

对于部署文件夹来说,一般是这样
bf7a14c5b69dd674258cab0882ad14dc44d963ca
部署
  • 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将不会生效

我们通过配置文件,此时你的目录应该是这样(新建文件)

d4304401eb2fc77d3503f9f4e11fff4a5640be85

配置文件

因为我们会往两个地方打包,那么自然,得俩配置文件了。

  • 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环境下,最后一种是错误的
c6e4bf6ee033888c42976edcaaa33ec26cc43407
  • 测试图

    这是因为,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
// --代表一个指令,与上边各个属性对应
// 此处展示的是经常会在网上看到的写法,并非我的写法

注意:

有一些在前边出现的属性,并没有被对应的写在cmd指令中。事实上,大多数配置都有两种写法,比如:

historyApiFallback: true对应--history-api-fallback

port:8080对应--port 8080

这些并不是我介绍的重点,如果你想要了解更多细节,请移步

小书api章节

如果你希望看到一些具体例子,请移步

webpack-githubexamples文件夹


如果我们使用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 访问,此时,我们在本地做出更改,然后浏览器将自动刷新,即可看到效果。
0385663d58cc57fab468defe9b7e4ccecee67b42
热加载引入

我们的页面整个都被刷新了,这显然是不高效的,为什么?
假如工程非常的庞大(实际开发中,往往都是‘非常庞大’),页面像我这篇文章一样,很长,,,修改一个很小的地方,如果页面整个刷新,第一,需要等待页面刷新完,很难过,第二,刷新出来,咱还得滚动半天到咱们修改的地方看效果。等等。都是不高效的。

所以我们需要另一个功能

  • 热加载

然而实现这个功能的过程中会遇到很多问题,接下来我将通过——问与答逐个说明

问题一:自动刷新功能的两种模式?以及如何配置?

这个是个用来承上启下的问题,首先,我们要更深入的了解一下自动刷新功能。

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模式,请看:

181f7cc9f4758388a31b433775cbe28f904c38cb

实时刷新测试
e1b8863cc2b55a3aea7a8a5ec74743ee09b1e8af
实时刷新测试2.png

但是,难道不能启动iframe模式了么?非也,请看

e39dd54721a19e0788f96ff7c11010595eb1270e

iframe

那么,我们并没有启动iframe模式啊?这与上边的hotOnly那一块说的一个意思,是我们手动启动了服务

我们可以关闭默认启动的inline模式:--no-inline命令

2c9871f563072df6ad276040de36daab1352d7a6

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.另一种可能——缓存

缓存简述

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

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

1540f4d2a3d45060888134b19f7a21f1ee906eec
缓存1.png


很明显,我们的bundle.js并不是从磁盘缓存中来的(细心的同学会说,哇,bundle.js为什么那么大?!这牵扯甚广,又是一篇文章的量啊)

e79c6e760eec0558b321256292de92d0eefe6394
缓存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中引用了它。

4088738c858cb291ff386ea6ec4f79ef36f75e79

Paste_Image.png

 
    

接下来,必须将webpack.config.js中,output下的publicPath与devServer下的publicPath设置为一样


// webpack.config.js
···
output: {
  ···
  publicPath: '/',
  ···
}
···
devServer: {
  ···
  publicPath: '/',
  ···
}
···

下面关于两种配置方式

  • cmd
    --hot
    设置它之后,启动服务,在浏览器可以看到:

e5cc8e0c0e06834e641e0853f917e04fb48d76cc
  • hot模块
    • HMR是hot模块产生
    • WDS是webpack-dev-server模块产生
  • 配置文件
    Node.js API方式需要做两个配置:

    • new webpack.HotModuleReplacementPlugin()加入到webpack配置文件的plugins项;
    • hot:true加入到webpack-dev-server的配置项(devServer)里面。
      两项都必须加大家可以自行验证一下,博主已经一一验证了。
89cc9b6ec42b8ef1a176191f4019b9614e30fd2c
Paste_Image.png
71cc5c8f3c196901f900c7903ec5564d149635d5
热加载nodeapi
  • 关于webpack/hot/only-dev-server前文也介绍了,虽然不加没错,但是还是加上比较好。

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

下边的写法是错误的!!!!!!

webpack/hot/dev-server添加到entry

8fb9bde89a18db8b8484675b37ec3aef7ee8150c

  • 热加载错误.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;

0bea70ea5164031c5a98fe633a0bc9d6a682140f
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库,也放上来

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

未完待续,敬请期待

原文发布时间为:2017年03月10日
原文作者:掘金
本文来源: 掘金 如需转载请联系原作者

 
      
目录
相关文章
|
6月前
|
缓存 前端开发
Vite 和 Webpack 的区别
Vite 和 Webpack 的区别
223 0
|
缓存 资源调度 编译器
原来是这样啊!浅谈webpack4和webpack5的区别
相对于webpack4,webpack5内置了很多plugin插件,比如、打包、压缩、缓存
738 1
|
21天前
|
缓存 前端开发 JavaScript
Webpack 4 和 Webpack 5 区别?
【10月更文挑战第23天】随着时间的推移,Webpack 可能会继续发展和演进,未来的版本可能会带来更多的新特性和改进。保持对技术发展的关注和学习,将有助于我们更好地应对不断变化的前端开发环境。
|
前端开发 JavaScript
vite和webpack 区别
vite和webpack 区别
83 0
|
6月前
|
前端开发 JavaScript 开发者
vite和webpack区别
【4月更文挑战第14天】Vite与Webpack都是前端构建工具,各有特点。Vite凭借原冷启动和模块热更新,适合现代前端项目,尤其是Vue、React等。它的配置简单,但社区支持较小。相比之下,Webpack拥有强大的插件系统和广泛社区支持,能适应各种项目需求,但配置复杂,启动慢。开发者应根据项目需求选择合适的工具。
156 2
|
6月前
|
JavaScript 开发者
Vite和Webpack的区别是什么
Vite和Webpack的区别是什么
|
6月前
|
JSON 前端开发 JavaScript
Vite和Webpack区别
Vite和Webpack区别
154 0
|
JavaScript 前端开发
WebPack实战 WebPack打包Vue项目
WebPack实战 WebPack打包Vue项目
WebPack实战 WebPack打包Vue项目
|
移动开发 JSON JavaScript
Vue_Webpack详解
Vue_Webpack详解
50 0
|
JavaScript 前端开发
webpack原理解析(一)实现一个简单的webpack
Webpack 是当下最热门的前端资源模块化管理和打包工具。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。webpack如此强大,其内部机制到底是怎样的,今天我们来一探究竟。