美文网首页react前端程序员
webpack-react之webpack篇(上)

webpack-react之webpack篇(上)

作者: 布蕾布蕾 | 来源:发表于2017-02-26 14:28 被阅读7112次

    构建一个小项目——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`也就可以运行了。
      },
    

    注意,不推荐全局安装 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]来生成对应打包堆特殊的名字。

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

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

    未完待续,敬请期待

    相关文章

      网友评论

      • coding野:好文章 详细 条理清楚明了 如果楼主是妹子就好了
      • a892c76ee1ef:不错,解决了很多疑问:+1:
      • 6770c2541b90:好文!!
        webpack中文文档:http://www.jqhtml.com/7626.html
      • d270297d9f08:webpack入门教程:http://t.cn/R9fQvdo
      • 9f791094e1ad:期待下篇
      • swnb:好文章,写得很详细,很有用,谢谢作者
      • 三杯两盏淡茶:没有下半部分了吗
        布蕾布蕾:@三杯两盏淡茶 下半部分还在酝酿,想接上个react项目,这样也符合题目。
        webpack的话,怎么写的高级一点(就跟各种cli里边的那种),webpack3 也要了解了解
        项目的话,一个管理系统,目前写了一半吧,技术栈是react + redux + ant design,主要是权限管理,数据列表展示和操作,过滤器。
        不敢保证啥时候能搞定,,,工作比较忙。
      • 725dbecef207:看完明白好多
        布蕾布蕾:@回忆想想而已 :relaxed:
      • 幸运星拾级而上:厉害了
        幸运星拾级而上:@布蕾布蕾 :smile: 应该的
        布蕾布蕾:@雪花大神 多谢支持
      • demo11:webpack不推荐全局安装,那是只需要局部安装吗?但是局部安装的话,会缺失一些依赖,网上百度了下说是全局安装是为了执行gulp任务,本地安装则是为了调用gulp插件的功能。

        注意,不推荐全局安装 webpacknpm i webpack g。这会锁定 webpack 到指定版本,并且在使用不同的 webpack 版本的项目中可能会导致构建失败。还有这句话怎么理解?
        布蕾布蕾:@断弦谁听 抱歉啊,最近实在有点忙,毕业了事太多,全局安装一般多用于需要在命令行里边使用的命令,像webpack这种,直接局部安装就可以了。你遇到的错误,我没遇到过类似的,不知道该怎么帮你,要是能还原错误,应该能分析分析。
        demo11:@布蕾布蕾 我的步骤是在github上下了一个项目demo,首先npm install安装依赖,但是就会报错了,所以我局部安装下webpack,接下里就会报一些依赖不存在,然后我全局安装下不存在的依赖就在了。我记得好像是什么loader-util,我全局安装的时候,就看见它安装了。
        你的意思是webpack不需要全局安装,但是我看教程上都说是先全局安装,后局部安装
        布蕾布蕾:局部安装会缺失依赖么?能具体说一说么?

        一般找不到依赖包就再安一遍。

        webpack版本不同,一些配置的写法也不一样,比如1.x跟2.x就有很大区别。假如说我们全局安装,那么的package.json就没有依赖的记录。别人用的时候,就不知道webpack的版本。如果他们的电脑也全局安装了webpack,但是与我们的版本不一样,就有可能出现问题。
        所以,我们要把webpack依赖写清楚.
        这个就是项目与本地环境的解耦,保证不会错
      • liamW:博主热替换那里应该是
        "dev": "webpack-dev-server --devtool eval-source-map --progress --colors --hot --open --content-base ./build "
        要不是不不会出效果的
        布蕾布蕾:我又看了下,没什么问题啊?--open 自动打开窗口,跟热替换没什么关系。你说的是哪里没有效果?能具体说下不?
      • 5ff3de61f332:感谢分享:grin:
      • 饥人谷_阿靖:最近在学react,感谢分享
        布蕾布蕾:@饥人谷_阿靖 :blush:
      • 温柔你要送嘻嘻:写的挺好的,
        布蕾布蕾:多谢肯定
      • HelloKang:屌!工作一年半的我自愧不如
        布蕾布蕾:@HelloKang 多谢肯定
      • 卡夫卡的react:非常好的一篇文章,比其他的都要新
        布蕾布蕾: @卡夫卡的react 具体一点是哪里卡住了?
        卡夫卡的react: @布蕾布蕾 但是那个--hot 命令我还是不知道怎么用,热刷新实现了,热替换没实现
        布蕾布蕾: @卡夫卡的react 😊😊😊

      本文标题:webpack-react之webpack篇(上)

      本文链接:https://www.haomeiwen.com/subject/boivwttx.html