哥本哈根的童话

哥本哈根的童话 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

哥本哈根的童话 赞了文章 · 2018-12-13

想写好前端,先练好内功

前言

封不平听在耳里,暗叫:“到这地步,我再能隐藏甚么?”仰天一声清啸,斜行而前,长剑横削直击,迅捷无比,未到五六招,剑势中已发出隐隐风声。他出剑越来越快,风声也是渐响,剑锋上所发出的一股劲气渐渐扩展,旁观众人只觉寒气逼人,脸上、手上被疾风刮得隐隐生疼,不由自主的后退,围在相斗两人身周的圈子渐渐扩大,竟有四五丈方圆。泰山派的一个道士在旁说道:“气宗的徒儿剑法高,剑宗的师叔内力强,这到底怎么搞的?华山派的气宗、剑宗,这可不是颠倒来玩了么?

《笑傲江湖》中的“剑宗余孽”封不平本想仗着有嵩山派撑腰,一举夺了华山掌门宝座。可打了半天剑法上占不了便宜,最后只能使出“狂风快剑”,企图以内力取胜。可见,任何高明武功若无内功心法相辅,也是徒劳无功。

说回前端,如今的前端技术栈就如同武侠小说中的江湖一样,各门各派自成一体,可谓“百花齐放”、“百家争鸣”。

这边 React 、Vue 、AngularJS 、JQuery 谁还都谈不上能一统江湖。“武林新贵” Flux 、Redux 、Mobx 们已经忙着争夺谁是数据流框架老大。Native 端 RN 刚偃旗息鼓,Weex 就大有“ I'm the everywhere ”之势。连备受争议的 GraphQL 内部都还有 Apollo、Relay 掐来掐去。

常听到身边的前端工程师抱怨,上周刚发布的 XXX 新版本文档还没看,今天 YYY 公司又发布了新框架,到底先学哪个?其实,无论是哪种框架哪项技术都是解决实际业务需求的手段、方法,和武林中各门各派的武功招式是一样的,各有所长,各有各的独到之处。

我们学习技术,除了了解具体使用方法,还需要掌握技术背后的设计理念和工程思想,这些背后的东西是我们技术选型的依据,是架构设计的基础,是软件系统的灵魂。这就好比是的武功中“内功心法”催动拳脚刀枪,一招一式,虎虎生风,纵有大敌当前,亦是淡然自若。

接下来分别谈一下三种工程思想,分别是:“开闭原则”、“函数式编程”和“消息机制”,这三种工程思想在后端开发中均有广泛的使用,容易被大家忽略的是目前很多前端技术框架也应用了这三种思想,以下结合具体案例分析,希望能够帮助大家加深对技术本身的理解。

开闭原则

说到面向对象设计,大部分人脑海中闪过的恐怕都是“23种设计模式”。设计模式代表的是业务场景中总结出的最佳实现方式,属于实践的范畴,在其之上是更为重要的“SOLID”五大原则:

  • Single Responsibility Principle 单一责任原则
  • The Open Closed Principle 开放封闭原则
  • The Liskov Substitution Principle 里氏替换原则
  • The Dependency Inversion Principle 依赖倒置原则
  • The Interface Segregation Principle 接口分离原则

SOLID 五大原则的出发点也是软件工程的终极目标:“高内聚、低耦合”。在后端开发中运用最多的是“依赖倒置原则”,与其相关的设计模式大约有5-6个。如下图所示:

上图也可以理解为从抽象概念到具体实践的逐步演进。

在前端技术框架中,运用最多的是“开放封闭原则”,我们先来看一下这条原则是怎么定义的:

A software artifact should be open for extension but closed for modification.

翻译过来就是:软件系统应当对扩展开放,对修改封闭(感觉像没说)。这里举一个简单的例子来说明开闭原则,先帮助大家理解概念:

public abstract class Shape
{
    public abstract double Area();    
}

public class Rectangle: Shape
{
    public double Width { get; set;}
    public double Height { get; set;}
    public override double Area()
    {
         return Width*Height'
    }
}

public class Circle: Shape
{
    public double Radius { get; set}
    public override double Area()
    {
        return Radius*Radius*PI;
    }
}

public double Area(Shape [] shapes)
{
    doubel area = 0;
    foreach (var shape in shapes)
    {
        area += shape.Area();
    }
    return area;
}

上例中无论场景如何扩展,Area 函数都无需修改,每个 Shape 类通过继承接口和多态特性,各自实现面积计算。

总结一下开闭原则就是:软件系统的核心逻辑都不应该轻易改变,否则会破坏系统的稳定性和增加测试成本。我们应当建立合适的抽象并统一接口,当业务需要扩展时,我们可以通过增加实体类来完成。

接下来我们看一个“开闭原则”在前端框架中的应用: Ant Design 组件库中的 Form 表单组件。

和其它组件不同,Form 组件并没有具体的形态,它更像是一个容器,提供了接入的标准,并提供了校验、表单提交等功能。绘制表单中的一项如下所示:

<FormItem>
  {getFieldDecorator('userName', {
     rules: [{ required: true, message: 'Please input your username!' }],
     })(<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
        placeholder="Username" />
   )}
</FormItem>

Ant Design 组件库中已经提供个几乎所有的常见表单组件,如:Select 、Checkbox 、Radio 、Cascader 等,但在实际业务中,我们还是会需要设计业务相关的表单项,Form 表单通过统一组件接口的方式满足了这个技术需求,具体规约如下:

自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件遵循以下的约定:

  1. 提供受控属性 value 或其它与 valuePropName 的值同名的属性。
  2. 提供 onChange 事件或 trigger 的值同名的事件。
  3. 不能是函数式组件。

具体例子

这正是“开闭原则”的一个典型实践案例,即表单核心逻辑(校验、提交等)保持不变并封装在 Form 组件中,自定义表单项只需要满足上述三条规约,就能平滑接入到 Form 组件中,和 Ant Design 原生组件契合在一起。

Ant Design 中的 Form 组件通过这样一个简洁的设计,完美提供了表单类型页面的统一解决方案。

函数式编程

随着人工智能、区块链、AR、VR、新零售等业务场景的出现,产品界面交互正在变得越来越复杂,这就对现代的前端开发者提出了更高的要求。如何快速、正确、高效地开发出高复杂度页面是目前前端技术最需要解决的问题。

函数式编程(以下简称 FP )凭借其高复用性、易测试性和与之带来的健壮性和简洁开始逐渐占据前端技术圈,我们发现越来越多的前端框架以 FP 为设计核心准则。

我们先简单介绍一下 FP,函数式编程的特征主要包括以下几个方面:

  • 函数为“一等公民”
  • 模块化、组合
  • 引用透明
  • 避免状态改变
  • 避免共享状态

JS 语言中的函数不同于 Java ,C/C++ 等语言, 可以被当做参数和返回值进行传递,因此天生具备“一等公民”特性。“模块化、组合”、“引用透明”、“避免状态改变”、“避免共享状态”这四个特征都需要通过特定代码模式实现。先举两个小例子:

找出字符串中率先出现的四个非数字字符?

非 FP 风格
var words = [], count = 0;
var text = str.split(''); 
for (var i = 0; couont < 4, i < text.length; i++) {   
  if(!text[i].match(/[0-9]/)) {
    words = words.concat(text[i]);
    count++;   
  }
} 
FP 风格
var words = str.split('').filter(function(x){
  return (!x.match(/[1-9]+/))}).slice(0,4);

第二段代码中使用的 js 数组方法 filter 和 slice,去掉了 for 循环,代码更简洁流畅。在写具体业务代码的时候,“模块化、组合”是 FP 最常用的技术,也是最重要的实现功能的手段。

分别实现数组所有元素相加、相乘、相与?

非 FP 风格
function plus(array) {
  var res = array[0];
  for (let i = 1; i < array.length; i++) {
    res += array[i];   
  }
}

function mul(array) {
  var res = array[0];
  for (let i = 1; i < array.length; i++) {
    res *= array[i];
  }
}

function and (array) {
  var res = array[0];
  for (let i = 1; i < array.length; i++) {
    res = res & array[i];
  }
}

plus(array);
mul(array);
and(array);
FP 风格
var ops = { 
  "plus": (x,y)=>x+y,
  "mul" : (x,y)=>x*y,
  "and" : (x,y)=>x&y
}

function operation(op, array) {
  return array.slice(1).reduce(ops[op], array[0]);
} 

operation("plus", array);
operation("mul",  array);
operation("and",  array); 

后一段代码中,使用了 reduce 函数代替了 for 循环,并将数值计算部分作为模块提取出来,当有新的计算类型时,只需要在 ops 对象中定义计算过程。这里就体现了 FP 中“模块化、组合”的特性。在 FP 风格下,我们习惯将复杂逻辑切割成一个个小模块,通过组合这些模块实现新的业务功能,当有新的需求到来时,我们尽可能地复用已有模块达到目标。FP 代码在复用性方面相比 OOD 有明显的优势。

React 中的 FP 思想

React 框架中,当用户操作 UI 或者 API 的返回带来了数据的改变,React 随即进行 virtual dom diff 计算得到 dom 的修改指令,对 dom 元素应用修改指令便得到最新的 html 界面,如下图所示:

不难发现,React 其实是应用数据对UI的一种映射,不同的数据会映射出不同样式的 UI 界面,我们可以得出如下的表达式:

$UI = React(data)$

没错,React 的本质其实是一种函数,并且还是符合 FP 要求的“引用透明”函数。所谓“引用透明”就是指函数的输出仅依赖函数参数,不受任何外部环境影响。这样的函数可测试性强,也非常容易进行组合。

在 React 的体系下,任何组件都可由一个个更小的组件构成,每个组件都只关心自己的输入,他们不断地接受新的数据并输出对应的新的UI界面。React 框架中常用的“高阶组件”可以看作引用透明”函数的组合模式。

在具体业务中我们通常还需要权衡 React 组件的复用性和开发体验,如果组件被拆分的过于细,固然复用性会提升,但文件数量会增加,对应的文档和沟通成本也会增加,这也是 FP 在实践过程中经常遭人诟病的点,即复用性提升后带来的额外开发成本。

消息机制

消息机制是软件工程中一个普遍运用的工程思想。“设计模式”中的观察者模式、Windows 操作系统底层、Spring 框架中的 ApplicationListener 模块、Objective-C 语言中的函数调用、都是通过消息机制驱动的。

使用消息机制最大的好处在于可以做到业务模块间安全解耦,模块间通过发送消息的方式进行协作,我们先举一个后端开发中的例子,下图是一个简单的预定系统的建模图,并没有使用消息机制:

在没有消息机制的情况下,用户模块需要知道订单模块的存在,并向起进行接口调用,同理订单模块需要向支付模块进行接口调用。这种设计下模块间是耦合的。

我们再来看一下使用消息机制的情况:

上图中,无论是客户下订单、支付还是预定都是通过消息的方式传递的,每个模块都是向一个消息处理器起发消息,同时也监听消息处理器发送回来的消息。在这种模式下,模块完全不知道其它模块的存在,彻底做到了解耦。

在前端业务开发中,我们经常也会用到 EventEmitter 库来进行消息传递。比如页面上有两块区域,一块用 React 框架渲染,一块用 D3 渲染的,当两块区域需要数据同步时,就可以使用消息机制进行通讯,保证页面数据整体一致。

如果你的业务中有不同生命周期的组件,建议采用消息机制进行管理,不仅消除了耦合,逻辑关系部分的代码也集中到了一个文件中,内聚性得到了提升

使用消息机制的一个附属产物就是中间件,我们可以为消息定制各种中间件,在中间中完成一些通用逻辑,让业务代码更精炼。

说到前端框架中消息机制的运用,当然首推 Redux 框架,在 Redux 框架中,任何数据交互都需要先转化为一个 action,由 action 去触发 reducer 和相关的 middleware 处理 action,改变数据,最终同步到页面 UI 上,如下图所示:

关于使用 Redux 的种种利弊,在各大社区中都有很深入的讨论,本文不再赘述。

总结

“开闭原则”、“函数式编程”、“消息机制”这个三个软件工程中重要的思想方法好比三套内功口诀,掌握了他们,才能更深刻地理解技术框架本身,发挥出技术框架的最大威力。

写到这里突然又想起《天龙八部》中一段:

乔峰眼见旁人退开,蓦地心念一动,呼的一拳打出,一招“冲阵斩将”,也正是“太祖长拳”中的招数。这一招姿式既潇洒大方已极,劲力更是刚中有柔,柔中有刚,武林高手毕生所盼望达到的拳术完美之境,竟在这一招中表露无遗。

一套平平无奇的“太祖长拳”在乔峰手中尽能有如此气象!

多少年以后,每当人们聊起金庸,聊起那个武侠世界,想必都会津津有味地回味、谈论起聚贤庄中这石破天惊的一拳。

参考

文章可随意转载,但请保留此 原文链接
非常欢迎有激情的你加入 ES2049 Studio,简历请发送至 caijun.hcj(at)alibaba-inc.com 。
查看原文

赞 39 收藏 27 评论 2

哥本哈根的童话 关注了用户 · 2018-11-08

充电大喵 @chongdianda

关注 79

哥本哈根的童话 收藏了文章 · 2018-11-07

2018你成长了么?一份给你的前端技术清单

2018 眼看就要过去了,今年的你相较去年技术上有怎样的收获呢?

记得年初的时候我给自己制定了一个学习计划,现在回顾来看完成度还不错。但仍有些遗憾,一些技术点没有时间去好好学习。

在学习中我发现,像文章这样的知识往往是碎片化的,而前端涉及到的面很多,如果不将这些知识有效梳理,则无法形成体系、相互串联。最后有一种东懂一块,西了解一点的感觉。因此,我结合工作体会抽象出了一些前端基础技术能力,并将这段时间学习或产出的一些不错的内容根据这些能力进行整理,形成了一份前端技术清单(github 地址)。

不论你是正在自学前端遇到了瓶颈,还是对某些技术熟练掌握但某些还未涉足,都希望这份清单能对你有所帮助。

由于个人精力有限,一些技术点的归纳可能有失偏颇,或者目前并未纳入进来,因此 github 上的清单内容 也会不断更新。目前只包含纯前端基础内容,NodeJS 、客户端泛前端、小程序、可视化等内容先留着坑吧。

清单内容↓↓↓

0. 年度报告

1. 基础拾遗

温故而知新,不知则习之,是以牢固根基。

1.1. JavaScript

1.2. CSS

1.3. 浏览器

2. 工程化与工具

软件规模的扩大带来了工程化的需求,前端也不例外。随着 NodeJS 的出现,前端工程师可以使用熟悉的 JS 快速开发所需的工具。工具链生态的繁荣也是前端圈繁荣的一个写照。

2.1. webpack

2.2. Gulp

2.3. Linter

2.4. 静态类型(Typescript/Flow)

2.5. Babel

2.6. CSS预处理与模块化

3. 性能优化

性能优化其实就是在理解浏览器的基础上“因地制宜”,因此可以配合1.3节“浏览器”部分进行理解。

强烈推荐把 Google Web 上性能优化 Tab 中的文章都通读一遍,其基本涵盖了现代浏览器中性能优化的所有点,非常系统。下面也摘录了其中一些个人认为非常不错的篇幅。

3.1. 加载性能

3.2. 运行时性能

3.3. 前端缓存

3.4. 性能调试与实践

3.5. 性能指标

4. 安全

很多安全风险老生常谈,但是往往到出现问题时,才会被重视或者意识到。

4.1. XSS

4.2. CSRF

4.3. CSP

4.4. HTTPS

4.5. 安全实录

5. 自动化测试

自动化测试是软件工程的重要部分之一,但却极容易被忽视。

5.1. 单元测试

5.2. 端到端测试 (E2E)

5.3. 其他

6. 框架与类库

如果说基础知识是道,那框架与工具可能就是术;学习与理解它们,但千万不要成为它们的奴隶。

6.1. React

6.2. Vue

6.3. Redux

6.4. RxJS

7. 新技术/方向

前端领域新技术、新方向层出不穷,这里汇总一些新技术方向;作为开发者需要多了解但是不要盲从

7.1. PWA

7.2. CSS Houdini

7.3. Web Components

7.4. 微前端(Micro Frontends)

7.5. HTTP/2

7.6. WebAssembly

8. 业务相关

在业务中往往还有一些与“业务无关”的场景需求,不论是什么业务几乎都会遇到;因此,在变与不变中,我们更需要去抽象出这些问题。

8.1. 数据打点上报

8.2. 前端监控

8.3. A/B测试

8.4. “服务端推”

8.5. 动效

9. 不归类的好文

开卷有益。
查看原文

哥本哈根的童话 赞了文章 · 2018-11-07

2018你成长了么?一份给你的前端技术清单

2018 眼看就要过去了,今年的你相较去年技术上有怎样的收获呢?

记得年初的时候我给自己制定了一个学习计划,现在回顾来看完成度还不错。但仍有些遗憾,一些技术点没有时间去好好学习。

在学习中我发现,像文章这样的知识往往是碎片化的,而前端涉及到的面很多,如果不将这些知识有效梳理,则无法形成体系、相互串联。最后有一种东懂一块,西了解一点的感觉。因此,我结合工作体会抽象出了一些前端基础技术能力,并将这段时间学习或产出的一些不错的内容根据这些能力进行整理,形成了一份前端技术清单(github 地址)。

不论你是正在自学前端遇到了瓶颈,还是对某些技术熟练掌握但某些还未涉足,都希望这份清单能对你有所帮助。

由于个人精力有限,一些技术点的归纳可能有失偏颇,或者目前并未纳入进来,因此 github 上的清单内容 也会不断更新。目前只包含纯前端基础内容,NodeJS 、客户端泛前端、小程序、可视化等内容先留着坑吧。

清单内容↓↓↓

0. 年度报告

1. 基础拾遗

温故而知新,不知则习之,是以牢固根基。

1.1. JavaScript

1.2. CSS

1.3. 浏览器

2. 工程化与工具

软件规模的扩大带来了工程化的需求,前端也不例外。随着 NodeJS 的出现,前端工程师可以使用熟悉的 JS 快速开发所需的工具。工具链生态的繁荣也是前端圈繁荣的一个写照。

2.1. webpack

2.2. Gulp

2.3. Linter

2.4. 静态类型(Typescript/Flow)

2.5. Babel

2.6. CSS预处理与模块化

3. 性能优化

性能优化其实就是在理解浏览器的基础上“因地制宜”,因此可以配合1.3节“浏览器”部分进行理解。

强烈推荐把 Google Web 上性能优化 Tab 中的文章都通读一遍,其基本涵盖了现代浏览器中性能优化的所有点,非常系统。下面也摘录了其中一些个人认为非常不错的篇幅。

3.1. 加载性能

3.2. 运行时性能

3.3. 前端缓存

3.4. 性能调试与实践

3.5. 性能指标

4. 安全

很多安全风险老生常谈,但是往往到出现问题时,才会被重视或者意识到。

4.1. XSS

4.2. CSRF

4.3. CSP

4.4. HTTPS

4.5. 安全实录

5. 自动化测试

自动化测试是软件工程的重要部分之一,但却极容易被忽视。

5.1. 单元测试

5.2. 端到端测试 (E2E)

5.3. 其他

6. 框架与类库

如果说基础知识是道,那框架与工具可能就是术;学习与理解它们,但千万不要成为它们的奴隶。

6.1. React

6.2. Vue

6.3. Redux

6.4. RxJS

7. 新技术/方向

前端领域新技术、新方向层出不穷,这里汇总一些新技术方向;作为开发者需要多了解但是不要盲从

7.1. PWA

7.2. CSS Houdini

7.3. Web Components

7.4. 微前端(Micro Frontends)

7.5. HTTP/2

7.6. WebAssembly

8. 业务相关

在业务中往往还有一些与“业务无关”的场景需求,不论是什么业务几乎都会遇到;因此,在变与不变中,我们更需要去抽象出这些问题。

8.1. 数据打点上报

8.2. 前端监控

8.3. A/B测试

8.4. “服务端推”

8.5. 动效

9. 不归类的好文

开卷有益。
查看原文

赞 682 收藏 589 评论 6

哥本哈根的童话 关注了用户 · 2018-10-29

darryrzhong @darryrzhong_5ac59892a5882

时光,不会辜负每一个平静努力的人

关注 121

哥本哈根的童话 赞了文章 · 2018-10-29

1024程序员节最新福利之2018最全H5前端资料集

前言

有好久没有写博客了,主要这段时间都沉迷学习无法自拔了,哈哈.自吹一波.

前两天不是1024节吗,所以就有很多福利出现了,当然每个人能都获得的信息都有所不同,这就是所谓的信息差.秉着好东西需要分享和开源的好习惯,所以来给你们送福利了.
其他福利

一、1024程序员节最新福利之2018最全java资料集合

二、1024程序员节最新福利之2018最全大数据资料集合

三、1024程序员节最新福利之2018最全Android资料集合

2018最全H5前端资料集合

一、HTML & CSS 核心教程:(103集实战教学,从入门到精通)

 http://pan.baidu.com/s/1pLwKZN1

二、尚硅谷JavaScript视频(140集实战教学,从入门到精通)

https://pan.baidu.com/s/1gfh9q8r

三、尚硅谷JavaScript高级视频

https://pan.baidu.com/s/1cLhs0u

四、尚硅谷jQuery视频

https://pan.baidu.com/s/1i5Gjxlj

五、尚硅谷NodeJS 视频

https://pan.baidu.com/s/1cnr0Cm

密码: sqku

六、尚硅谷MongoDB 视频

 https://pan.baidu.com/s/1mirGFyw

密码: nwe1

七、尚硅谷Zepto 视频

https://pan.baidu.com/s/1o7PSymu

密码: e86p

八、尚硅谷AngularJS 视频

https://pan.baidu.com/s/1o85jOVK

密码: 7vi2

九、尚硅谷ES5_6_7 视频

 https://pan.baidu.com/s/1i4Z5VNZ

密码: 3fuy

十、尚硅谷JS模块化 视频

 https://pan.baidu.com/s/1skO0tJZ

密码: tekn

十一、最新尚硅谷自动化构建工具 视频

(1)webpack 链接: https://pan.baidu.com/s/1kUG5cLT 密码: ru45

(2)Gulp 链接: https://pan.baidu.com/s/1hsMqZkS 密码: nprj

(3)Grunt 链接: https://pan.baidu.com/s/1bGhxL0 密码: m7kq

十二、尚硅谷React视频

https://pan.baidu.com/s/1TnD2BNqhNWKWyb6v3VoZfw

 密码:xz7c

十三、尚硅谷CSS2.1 视频

https://pan.baidu.com/s/1ggA6SPt

密码:jybl

十四、最新尚硅谷CSS3 视频

https://pan.baidu.com/s/1bqiUHYZ

 密码:jojr

十五、最新尚硅谷less 视频

https://pan.baidu.com/s/1jJFk5MI

密码:izzk

十六、尚硅谷bootstrap 视频

https://pan.baidu.com/s/1eTxHHN8

密码:hzet

十七、尚硅谷HTML5核心视频

https://pan.baidu.com/s/18hy_J8FdmBYabVGU8BqVKA

密码:3fhg

十八、尚硅谷HTML5实战视频

https://pan.baidu.com/s/1ipfFFhtWGvNuCMicxnzQdw

密码:dehi

十九、最新尚硅谷HTML5项目-谷粒音乐

  https://pan.baidu.com/s/1u6LBOWa9QCpETeC3gjzojw

密码:plb6

二十、最新尚硅谷项目硅谷直聘

https://pan.baidu.com/s/1h7H6WwLjyYVstlYix7LuxA

 密码:wh98

二十一、最新尚硅谷VUE技术核心视频    

https://pan.baidu.com/s/1M6i5gcjxsooj6ejOhOJ2zw

密码:rf35

二十二:最新尚硅谷VUE项目-硅谷外卖

  https://pan.baidu.com/s/1siO6Anl_LeyfS2hcv6rStw

 密码:b6q5

二十三:最新尚硅谷微信小程序视频

https://pan.baidu.com/s/1dyM3AUC-UPVBjU9LcVMF5w

密码:0vhr

二十四、HTML5实战_天猫商城品牌墙

https://pan.baidu.com/s/1hr4v8Pm

二十五、HTML5特效实战

https://pan.baidu.com/s/1kVBrpZp

二十六、CSS3特效实战

https://pan.baidu.com/s/1dESOjFr

二十七、HTML5实战之Canvas刮刮卡

https://pan.baidu.com/s/1ge7Lw9l

H5前端资料集合都在这了,赶快收藏吧.最后记得给我小心心哟!

欢迎关注作者darryrzhong,更多干货等你来拿哟.

请赏个小红心!因为你的鼓励是我写作的最大动力!

更多精彩文章请关注
查看原文

赞 167 收藏 146 评论 4

哥本哈根的童话 赞了回答 · 2018-10-25

解决什么是前端渲染?什么是服务端渲染?两者区别及应用场景?

IT界有一个不好的习惯就是会造一个花里胡哨的名词让你不知道具体细节:

后端渲染:后端的程序在把html页面吐给前端之前,先把html页面上的特定区域,特定符号,给用数据填充过,再扔给前端,这就是后端渲染,所谓渲染,你可以理解一种修改,渲染这词最早来源于游戏领域,游戏领域又来源于现实画画,渲染嘛,拿着颜料往纸上涂便是。以前绝大部分服务器都是这个模式

前端渲染:后端的html页面作为静态文件存在,前端请求时后端不对该文件做任何内容上的修改,直接以资源的方式返回给前端,前端拿到页面后,根据写在html页面上的js代码,对该html的内容进行修改(涂颜料)。这就是前段渲染

关注 12 回答 3

哥本哈根的童话 收藏了文章 · 2018-08-21

入门 Webpack,看这篇就够了

2018年8月25日更新,目前 webpack 已经更新值 4.17.1 ,本文所用到的各种库或多或少有些过时,跟着代码操作下来可能会遇到各种问题,不过 webpack 的主体思想没变,所以还是希望本文对新学 webpack 的你,有所帮助。此外用基于 webpack 4.17.1 写了一个简单的demo,如果遇到啥问题,可以参考,之后应该会逐步来完善这个demo,如果有啥通用的想实现的功能,也可以在里面提 issue。

2017年12月7日更新,添加了clean-webpack-plugin,babel-env-preset,添加本文涉及到的所有代码的示例,如果你在学习过程中出错了,可点击此处参考(有些过时了,不要再 fork 了)

写在前面的话

阅读本文之前,先看下面这个webpack的配置文件,如果每一项你都懂,那本文能带给你的收获也许就比较有限,你可以快速浏览或直接跳过;如果你和十天前的我一样,对很多选项存在着疑惑,那花一段时间慢慢阅读本文,你的疑惑一定一个一个都会消失;如果你以前没怎么接触过Webpack,而你又你对webpack感兴趣,那么动手跟着本文中那个贯穿始终的例子写一次,写完以后你会发现你已明明白白的走进了Webpack的大门。
// 一个常见的`webpack`配置文件
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
        entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件
        output: {
            path: __dirname + "/build",
            filename: "bundle-[hash].js"
        },
        devtool: 'none',
        devServer: {
            contentBase: "./public", //本地服务器所加载的页面所在的目录
            historyApiFallback: true, //不跳转
            inline: true,
            hot: true
        },
        module: {
            rules: [{
                    test: /(\.jsx|\.js)$/,
                    use: {
                        loader: "babel-loader"
                    },
                    exclude: /node_modules/
                }, {
                    test: /\.css$/,
                    use: ExtractTextPlugin.extract({
                        fallback: "style-loader",
                        use: [{
                            loader: "css-loader",
                            options: {
                                modules: true,
                                localIdentName: '[name]__[local]--[hash:base64:5]'
                            }
                        }, {
                            loader: "postcss-loader"
                        }],
                    })
                }
            }
        ]
    },
    plugins: [
        new webpack.BannerPlugin('版权所有,翻版必究'),
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html" //new 一个这个插件的实例,并传入相关的参数
        }),
        new webpack.optimize.OccurrenceOrderPlugin(),
        new webpack.optimize.UglifyJsPlugin(),
        new ExtractTextPlugin("style.css")
    ]
};

什么是WebPack,为什么要使用它?

为什要使用WebPack

现今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包。为了简化开发的复杂度,前端社区涌现出了很多好的实践方法

  • 模块化,让我们可以把复杂的程序细化为小的文件;
  • 类似于TypeScript这种在JavaScript基础上拓展的开发语言:使我们能够实现目前版本的JavaScript不能直接使用的特性,并且之后还能转换为JavaScript文件使浏览器可以识别;
  • Scss,less等CSS预处理器
  • ...

这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常繁琐的,这就为WebPack类的工具的出现提供了需求。

什么是Webpack

WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。

WebPack和Grunt以及Gulp相比有什么特性

其实Webpack和另外两个并没有太多的可比性,Gulp/Grunt是一种能够优化前端的开发流程的工具,而WebPack是一种模块化的解决方案,不过Webpack的优点使得Webpack在很多场景下可以替代Gulp/Grunt类的工具。

Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务。
Grunt和Gulp的工作流程

Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。
Webpack工作方式

如果实在要把二者进行比较,Webpack的处理速度更快更直接,能打包更多不同类型的文件。

开始使用Webpack

初步了解了Webpack工作方式后,我们一步步的开始学习使用Webpack。

安装

Webpack可以使用npm安装,新建一个空的练习文件夹(此处命名为webpack sample project),在终端中转到该文件夹后执行下述指令就可以完成安装。

//全局安装
npm install -g webpack
//安装到你的项目目录
npm install --save-dev webpack

正式使用Webpack前的准备

  1. 在上述练习文件夹中创建一个package.json文件,这是一个标准的npm说明文件,里面蕴含了丰富的信息,包括当前项目的依赖模块,自定义的脚本任务等等。在终端中使用npm init命令可以自动创建这个package.json文件
npm init

输入这个命令后,终端会问你一系列诸如项目名称,项目描述,作者等信息,不过不用担心,如果你不准备在npm中发布你的模块,这些问题的答案都不重要,回车默认即可。

  1. package.json文件已经就绪,我们在本项目中安装Webpack作为依赖包
// 安装Webpack
npm install --save-dev webpack
  1. 回到之前的空文件夹,并在里面创建两个文件夹,app文件夹和public文件夹,app文件夹用来存放原始数据和我们将写的JavaScript模块,public文件夹用来存放之后供浏览器读取的文件(包括使用webpack打包生成的js文件以及一个index.html文件)。接下来我们再创建三个文件:
  • index.html --放在public文件夹中;
  • Greeter.js-- 放在app文件夹中;
  • main.js-- 放在app文件夹中;

此时项目结构如下图所示
项目结构

我们在index.html文件中写入最基础的html代码,它在这里目的在于引入打包后的js文件(这里我们先把之后打包后的js文件命名为bundle.js,之后我们还会详细讲述)。

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Webpack Sample Project</title>
  </head>
  <body>
    <div id='root'>
    </div>
    <script data-original="bundle.js"></script>
  </body>
</html>

我们在Greeter.js中定义一个返回包含问候信息的html元素的函数,并依据CommonJS规范导出这个函数为一个模块:

// Greeter.js
module.exports = function() {
  var greet = document.createElement('div');
  greet.textContent = "Hi there and greetings!";
  return greet;
};

main.js文件中我们写入下述代码,用以把Greeter模块返回的节点插入页面。

//main.js 
const greeter = require('./Greeter.js');
document.querySelector("#root").appendChild(greeter());

正式使用Webpack

webpack可以在终端中使用,在基本的使用方法如下:

# {extry file}出填写入口文件的路径,本文中就是上述main.js的路径,
# {destination for bundled file}处填写打包文件的存放路径
# 填写路径的时候不用添加{}
webpack {entry file} {destination for bundled file}

指定入口文件后,webpack将自动识别项目所依赖的其它文件,不过需要注意的是如果你的webpack不是全局安装的,那么当你在终端中使用此命令时,需要额外指定其在node_modules中的地址,继续上面的例子,在终端中输入如下命令

# webpack非全局安装的情况
node_modules/.bin/webpack app/main.js public/bundle.js

结果如下

使用命令行打包

可以看出webpack同时编译了main.jsGreeter,js,现在打开index.html,可以看到如下结果
htmlResult1

有没有很激动,已经成功的使用Webpack打包了一个文件了。不过在终端中进行复杂的操作,其实是不太方便且容易出错的,接下来看看Webpack的另一种更常见的使用方法。

通过配置文件来使用Webpack

Webpack拥有很多其它的比较高级的功能(比如说本文后面会介绍的loadersplugins),这些功能其实都可以通过命令行模式实现,但是正如前面提到的,这样不太方便且容易出错的,更好的办法是定义一个配置文件,这个配置文件其实也是一个简单的JavaScript模块,我们可以把所有的与打包相关的信息放在里面。

继续上面的例子来说明如何写这个配置文件,在当前练习文件夹的根目录下新建一个名为webpack.config.js的文件,我们在其中写入如下所示的简单配置代码,目前的配置主要涉及到的内容是入口文件路径和打包后文件的存放路径。

module.exports = {
  entry:  __dirname + "/app/main.js",//已多次提及的唯一入口文件
  output: {
    path: __dirname + "/public",//打包后的文件存放的地方
    filename: "bundle.js"//打包后输出文件的文件名
  }
}
:“__dirname”是node.js中的一个全局变量,它指向当前执行脚本所在的目录。

有了这个配置之后,再打包文件,只需在终端里运行webpack(非全局安装需使用node_modules/.bin/webpack)命令就可以了,这条命令会自动引用webpack.config.js文件中的配置选项,示例如下:

配合配置文件进行打包

又学会了一种使用Webpack的方法,这种方法不用管那烦人的命令行参数,有没有感觉很爽。如果我们可以连webpack(非全局安装需使用node_modules/.bin/webpack)这条命令都可以不用,那种感觉会不会更爽~,继续看下文。

更快捷的执行打包任务

在命令行中输入命令需要代码类似于node_modules/.bin/webpack这样的路径其实是比较烦人的,不过值得庆幸的是npm可以引导任务执行,对npm进行配置后可以在命令行中使用简单的npm start命令来替代上面略微繁琐的命令。在package.json中对scripts对象进行相关设置即可,设置方法如下。

{
  "name": "webpack-sample-project",
  "version": "1.0.0",
  "description": "Sample webpack project",
  "scripts": {
    "start": "webpack" // 修改的是这里,JSON文件不支持注释,引用时请清除
  },
  "author": "zhang",
  "license": "ISC",
  "devDependencies": {
    "webpack": "3.10.0"
  }
}
注:package.json中的script会安装一定顺序寻找命令对应位置,本地的node_modules/.bin路径就在这个寻找清单中,所以无论是全局还是局部安装的Webpack,你都不需要写前面那指明详细的路径了。

npm的start命令是一个特殊的脚本名称,其特殊性表现在,在命令行中使用npm start就可以执行其对于的命令,如果对应的此脚本名称不是start,想要在命令行中运行时,需要这样用npm run {script name}npm run build,我们在命令行中输入npm start试试,输出结果如下:

使用npm start 打包代码

现在只需要使用npm start就可以打包文件了,有没有觉得webpack也不过如此嘛,不过不要太小瞧webpack,要充分发挥其强大的功能我们需要修改配置文件的其它选项,一项项来看。

Webpack的强大功能

生成Source Maps(使调试更容易)

开发总是离不开调试,方便的调试能极大的提高开发效率,不过有时候通过打包后的文件,你是不容易找到出错了的地方,对应的你写的代码的位置的,Source Maps就是来帮我们解决这个问题的。

通过简单的配置,webpack就可以在打包时为我们生成的source maps,这为我们提供了一种对应编译文件和源文件的方法,使得编译后的代码可读性更高,也更容易调试。

webpack的配置文件中配置source maps,需要配置devtool,它有以下四种不同的配置选项,各具优缺点,描述如下:

devtool选项配置结果
source-map在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map,但是它会减慢打包速度;
cheap-module-source-map在一个单独的文件中生成一个不带列映射的map,不带列映射提高了打包速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便;
eval-source-map使用eval打包源文件模块,在同一个文件中生成干净的完整的source map。这个选项可以在不影响构建速度的前提下生成完整的sourcemap,但是对打包后输出的JS文件的执行具有性能和安全的隐患。在开发阶段这是一个非常好的选项,在生产阶段则一定不要启用这个选项;
cheap-module-eval-source-map这是在打包文件时最快的生成source map的方法,生成的Source Map 会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map选项具有相似的缺点;

正如上表所述,上述选项由上到下打包速度越来越快,不过同时也具有越来越多的负面作用,较快的打包速度的后果就是对打包后的文件的的执行有一定影响。

对小到中型的项目中,eval-source-map是一个很好的选项,再次强调你只应该开发阶段使用它,我们继续对上文新建的webpack.config.js,进行如下配置:

module.exports = {
  devtool: 'eval-source-map',
  entry:  __dirname + "/app/main.js",
  output: {
    path: __dirname + "/public",
    filename: "bundle.js"
  }
}
cheap-module-eval-source-map方法构建速度更快,但是不利于调试,推荐在大型项目考虑时间成本时使用。

使用webpack构建本地服务器

想不想让你的浏览器监听你的代码的修改,并自动刷新显示修改后的结果,其实Webpack提供一个可选的本地开发服务器,这个本地服务器基于node.js构建,可以实现你想要的这些功能,不过它是一个单独的组件,在webpack中进行配置之前需要单独安装它作为项目依赖

npm install --save-dev webpack-dev-server

devserver作为webpack配置选项中的一项,以下是它的一些配置选项,更多配置可参考这里

devserver的配置选项功能描述
contentBase默认webpack-dev-server会为根文件夹提供本地服务器,如果想为另外一个目录下的文件提供本地服务器,应该在这里设置其所在目录(本例设置到“public"目录)
port设置默认监听端口,如果省略,默认为”8080“
inline设置为true,当源文件改变时会自动刷新页面
historyApiFallback在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html

把这些命令加到webpack的配置文件中,现在的配置文件webpack.config.js如下所示

module.exports = {
  devtool: 'eval-source-map',

  entry:  __dirname + "/app/main.js",
  output: {
    path: __dirname + "/public",
    filename: "bundle.js"
  },

  devServer: {
    contentBase: "./public",//本地服务器所加载的页面所在的目录
    historyApiFallback: true,//不跳转
    inline: true//实时刷新
  } 
}

package.json中的scripts对象中添加如下命令,用以开启本地服务器:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack",
    "server": "webpack-dev-server --open"
  },

在终端中输入npm run server即可在本地的8080端口查看结果

开启本地服务器

Loaders

鼎鼎大名的Loaders登场了!

Loaderswebpack提供的最激动人心的功能之一了。通过使用不同的loaderwebpack有能力调用外部的脚本或工具,实现对不同格式的文件的处理,比如说分析转换scss为css,或者把下一代的JS文件(ES6,ES7)转换为现代浏览器兼容的JS文件,对React的开发而言,合适的Loaders可以把React的中用到的JSX文件转换为JS文件。

Loaders需要单独安装并且需要在webpack.config.js中的modules关键字下进行配置,Loaders的配置包括以下几方面:

  • test:一个用以匹配loaders所处理文件的拓展名的正则表达式(必须)
  • loader:loader的名称(必须)
  • include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
  • query:为loaders提供额外的设置选项(可选)

不过在配置loader之前,我们把Greeter.js里的问候消息放在一个单独的JSON文件里,并通过合适的配置使Greeter.js可以读取该JSON文件的值,各文件修改后的代码如下:

在app文件夹中创建带有问候信息的JSON文件(命名为config.json)

{
  "greetText": "Hi there and greetings from JSON!"
}

更新后的Greeter.js

var config = require('./config.json');

module.exports = function() {
  var greet = document.createElement('div');
  greet.textContent = config.greetText;
  return greet;
};
由于webpack3.*/webpack2.*已经内置可处理JSON文件,这里我们无需再添加webpack1.*需要的json-loader。在看如何具体使用loader之前我们先看看Babel是什么?

Babel

Babel其实是一个编译JavaScript的平台,它可以编译代码帮你达到以下目的:

  • 让你能使用最新的JavaScript代码(ES6,ES7...),而不用管新标准是否被当前使用的浏览器完全支持;
  • 让你能使用基于JavaScript进行了拓展的语言,比如React的JSX;

Babel的安装与配置

Babel其实是几个模块化的包,其核心功能位于称为babel-core的npm包中,webpack可以把其不同的包整合在一起使用,对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析Es6的babel-env-preset包和解析JSX的babel-preset-react包)。

我们先来一次性安装这些依赖包

// npm一次性安装多个依赖模块,模块之间用空格隔开
npm install --save-dev babel-core babel-loader babel-preset-env babel-preset-react

webpack中配置Babel的方法如下:

module.exports = {
    entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
    output: {
        path: __dirname + "/public",//打包后的文件存放的地方
        filename: "bundle.js"//打包后输出文件的文件名
    },
    devtool: 'eval-source-map',
    devServer: {
        contentBase: "./public",//本地服务器所加载的页面所在的目录
        historyApiFallback: true,//不跳转
        inline: true//实时刷新
    },
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: [
                            "env", "react"
                        ]
                    }
                },
                exclude: /node_modules/
            }
        ]
    }
};

现在你的webpack的配置已经允许你使用ES6以及JSX的语法了。继续用上面的例子进行测试,不过这次我们会使用React,记得先安装 React 和 React-DOM

npm install --save react react-dom

接下来我们使用ES6的语法,更新Greeter.js并返回一个React组件

//Greeter,js
import React, {Component} from 'react'
import config from './config.json';

class Greeter extends Component{
  render() {
    return (
      <div>
        {config.greetText}
      </div>
    );
  }
}

export default Greeter

修改main.js如下,使用ES6的模块定义和渲染Greeter模块

// main.js
import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';

render(<Greeter />, document.getElementById('root'));

重新使用npm start打包,如果之前打开的本地服务器没有关闭,你应该可以在localhost:8080下看到与之前一样的内容,这说明reactes6被正常打包了。

localhost:8080

Babel的配置

Babel其实可以完全在 webpack.config.js 中进行配置,但是考虑到babel具有非常多的配置选项,在单一的webpack.config.js文件中进行配置往往使得这个文件显得太复杂,因此一些开发者支持把babel的配置选项放在一个单独的名为 ".babelrc" 的配置文件中。我们现在的babel的配置并不算复杂,不过之后我们会再加一些东西,因此现在我们就提取出相关部分,分两个配置文件进行配置(webpack会自动调用.babelrc里的babel配置选项),如下:

module.exports = {
    entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
    output: {
        path: __dirname + "/public",//打包后的文件存放的地方
        filename: "bundle.js"//打包后输出文件的文件名
    },
    devtool: 'eval-source-map',
    devServer: {
        contentBase: "./public",//本地服务器所加载的页面所在的目录
        historyApiFallback: true,//不跳转
        inline: true//实时刷新
    },
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            }
        ]
    }
};
//.babelrc
{
  "presets": ["react", "env"]
}

到目前为止,我们已经知道了,对于模块,Webpack能提供非常强大的处理功能,那那些是模块呢。

一切皆模块

Webpack有一个不可不说的优点,它把所有的文件都都当做模块处理,JavaScript代码,CSS和fonts以及图片等等通过合适的loader都可以被处理。

CSS

webpack提供两个工具处理样式表,css-loaderstyle-loader,二者处理的任务不同,css-loader使你能够使用类似@importurl(...)的方法实现 require()的功能,style-loader将所有的计算后的样式加入页面中,二者组合在一起使你能够把样式表嵌入webpack打包后的JS文件中。

继续上面的例子

//安装
npm install --save-dev style-loader css-loader
//使用
module.exports = {

   ...
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader"
                    }
                ]
            }
        ]
    }
};
请注意这里对同一个文件引入多个loader的方法。

接下来,在app文件夹里创建一个名字为"main.css"的文件,对一些元素设置样式

/* main.css */
html {
  box-sizing: border-box;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
}

*, *:before, *:after {
  box-sizing: inherit;
}

body {
  margin: 0;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

h1, h2, h3, h4, h5, h6, p, ul {
  margin: 0;
  padding: 0;
}

我们这里例子中用到的webpack只有单一的入口,其它的模块需要通过 import, require, url等与入口文件建立其关联,为了让webpack能找到”main.css“文件,我们把它导入”main.js “中,如下

//main.js
import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';

import './main.css';//使用require导入css文件

render(<Greeter />, document.getElementById('root'));
通常情况下,css会和js打包到同一个文件中,并不会打包为一个单独的css文件,不过通过合适的配置webpack也可以把css打包为单独的文件的。

上面的代码说明webpack是怎么把css当做模块看待的,咱们继续看一个更加真实的css模块实践。

CSS module

在过去的一些年里,JavaScript通过一些新的语言特性,更好的工具以及更好的实践方法(比如说模块化)发展得非常迅速。模块使得开发者把复杂的代码转化为小的,干净的,依赖声明明确的单元,配合优化工具,依赖管理和加载管理可以自动完成。

不过前端的另外一部分,CSS发展就相对慢一些,大多的样式表却依旧巨大且充满了全局类名,维护和修改都非常困难。

被称为CSS modules的技术意在把JS的模块化思想带入CSS中来,通过CSS模块,所有的类名,动画名默认都只作用于当前模块。Webpack对CSS模块化提供了非常好的支持,只需要在CSS loader中进行简单配置即可,然后就可以直接把CSS的类名传递到组件的代码中,这样做有效避免了全局污染。具体的代码如下

module.exports = {

    ...

    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true, // 指定启用css modules
                            localIdentName: '[name]__[local]--[hash:base64:5]' // 指定css的类名格式
                        }
                    }
                ]
            }
        ]
    }
};

我们在app文件夹下创建一个Greeter.css文件来进行一下测试

/* Greeter.css */
.root {
  background-color: #eee;
  padding: 10px;
  border: 3px solid #ccc;
}

导入.root到Greeter.js中

import React, {Component} from 'react';
import config from './config.json';
import styles from './Greeter.css';//导入

class Greeter extends Component{
  render() {
    return (
      <div className={styles.root}> //使用cssModule添加类名的方法
        {config.greetText}
      </div>
    );
  }
}

export default Greeter

放心使用把,相同的类名也不会造成不同组件之间的污染。

应用了css module后的样式

CSS modules 也是一个很大的主题,有兴趣的话可以去其官方文档了解更多。

CSS预处理器

SassLess 之类的预处理器是对原生CSS的拓展,它们允许你使用类似于variables, nesting, mixins, inheritance等不存在于CSS中的特性来写CSS,CSS预处理器可以这些特殊类型的语句转化为浏览器可识别的CSS语句,

你现在可能都已经熟悉了,在webpack里使用相关loaders进行配置就可以使用了,以下是常用的CSS 处理loaders:

  • Less Loader
  • Sass Loader
  • Stylus Loader

不过其实也存在一个CSS的处理平台-PostCSS,它可以帮助你的CSS实现更多的功能,在其官方文档可了解更多相关知识。

举例来说如何使用PostCSS,我们使用PostCSS来为CSS代码自动添加适应不同浏览器的CSS前缀。

首先安装postcss-loaderautoprefixer(自动添加前缀的插件)

npm install --save-dev postcss-loader autoprefixer

接下来,在webpack配置文件中添加postcss-loader,在根目录新建postcss.config.js,并添加如下代码之后,重新使用npm start打包时,你写的css会自动根据Can i use里的数据添加不同前缀了。

//webpack.config.js
module.exports = {
    ...
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true
                        }
                    }, {
                        loader: "postcss-loader"
                    }
                ]
            }
        ]
    }
}
// postcss.config.js
module.exports = {
    plugins: [
        require('autoprefixer')
    ]
}

至此,本文已经谈论了处理JS的Babel和处理CSS的PostCSS的基本用法,它们其实也是两个单独的平台,配合webpack可以很好的发挥它们的作用。接下来介绍Webpack中另一个非常重要的功能-Plugins

插件(Plugins)

插件(Plugins)是用来拓展Webpack功能的,它们会在整个构建过程中生效,执行相关的任务。
Loaders和Plugins常常被弄混,但是他们其实是完全不同的东西,可以这么来说,loaders是在打包构建过程中用来处理源文件的(JSX,Scss,Less..),一次处理一个,插件并不直接操作单个文件,它直接对整个构建过程其作用。

Webpack有很多内置插件,同时也有很多第三方插件,可以让我们完成更加丰富的功能。

使用插件的方法

要使用某个插件,我们需要通过npm安装它,然后要做的就是在webpack配置中的plugins关键字部分添加该插件的一个实例(plugins是一个数组)继续上面的例子,我们添加了一个给打包后代码添加版权声明的插件

const webpack = require('webpack');

module.exports = {
...
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true
                        }
                    }, {
                        loader: "postcss-loader"
                    }
                ]
            }
        ]
    },
    plugins: [
        new webpack.BannerPlugin('版权所有,翻版必究')
    ],
};

通过这个插件,打包后的JS文件显示如下

版权所有,翻版必究

这就是webpack插件的基础用法了,下面给大家推荐几个常用的插件

HtmlWebpackPlugin

这个插件的作用是依据一个简单的index.html模板,生成一个自动引用你打包后的JS文件的新index.html。这在每次生成的js文件名称不同时非常有用(比如添加了hash值)。

安装

npm install --save-dev html-webpack-plugin

这个插件自动完成了我们之前手动做的一些事情,在正式使用之前需要对一直以来的项目结构做一些更改:

  1. 移除public文件夹,利用此插件,index.html文件会自动生成,此外CSS已经通过前面的操作打包到JS中了。
  2. 在app目录下,创建一个index.tmpl.html文件模板,这个模板包含title等必须元素,在编译过程中,插件会依据此模板生成最终的html页面,会自动添加所依赖的 css, js,favicon等文件,index.tmpl.html中的模板源代码如下:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Webpack Sample Project</title>
  </head>
  <body>
    <div id='root'>
    </div>
  </body>
</html>

3.更新webpack的配置文件,方法同上,新建一个build文件夹用来存放最终的输出文件

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
    output: {
        path: __dirname + "/build",
        filename: "bundle.js"
    },
    devtool: 'eval-source-map',
    devServer: {
        contentBase: "./public",//本地服务器所加载的页面所在的目录
        historyApiFallback: true,//不跳转
        inline: true//实时刷新
    },
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true
                        }
                    }, {
                        loader: "postcss-loader"
                    }
                ]
            }
        ]
    },
    plugins: [
        new webpack.BannerPlugin('版权所有,翻版必究'),
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html"//new 一个这个插件的实例,并传入相关的参数
        })
    ],
};

再次执行npm start你会发现,build文件夹下面生成了bundle.jsindex.html

build文件夹

Hot Module Replacement

Hot Module Replacement(HMR)也是webpack里很有用的一个插件,它允许你在修改组件代码后,自动刷新实时预览修改后的效果。

在webpack中实现HMR也很简单,只需要做两项配置

  1. 在webpack配置文件中添加HMR插件;
  2. 在Webpack Dev Server中添加“hot”参数;

不过配置完这些后,JS模块其实还是不能自动热加载的,还需要在你的JS模块中执行一个Webpack提供的API才能实现热加载,虽然这个API不难使用,但是如果是React模块,使用我们已经熟悉的Babel可以更方便的实现功能热加载。

整理下我们的思路,具体实现方法如下

  • Babelwebpack是独立的工具
  • 二者可以一起工作
  • 二者都可以通过插件拓展功能
  • HMR是一个webpack插件,它让你能浏览器中实时观察模块修改后的效果,但是如果你想让它工作,需要对模块进行额外的配额;
  • Babel有一个叫做react-transform-hrm的插件,可以在不对React模块进行额外的配置的前提下让HMR正常工作;

还是继续上例来实际看看如何配置

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
    output: {
        path: __dirname + "/build",
        filename: "bundle.js"
    },
    devtool: 'eval-source-map',
    devServer: {
        contentBase: "./public",//本地服务器所加载的页面所在的目录
        historyApiFallback: true,//不跳转
        inline: true,
        hot: true
    },
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true
                        }
                    }, {
                        loader: "postcss-loader"
                    }
                ]
            }
        ]
    },
    plugins: [
        new webpack.BannerPlugin('版权所有,翻版必究'),
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html"//new 一个这个插件的实例,并传入相关的参数
        }),
        new webpack.HotModuleReplacementPlugin()//热加载插件
    ],
};
   

安装react-transform-hmr

npm install --save-dev babel-plugin-react-transform react-transform-hmr

配置Babel

// .babelrc
{
  "presets": ["react", "env"],
  "env": {
    "development": {
    "plugins": [["react-transform", {
       "transforms": [{
         "transform": "react-transform-hmr",
         
         "imports": ["react"],
         
         "locals": ["module"]
       }]
     }]]
    }
  }
}

现在当你使用React时,可以热加载模块了,每次保存就能在浏览器上看到更新内容。

产品阶段的构建

目前为止,我们已经使用webpack构建了一个完整的开发环境。但是在产品阶段,可能还需要对打包的文件进行额外的处理,比如说优化,压缩,缓存以及分离CSS和JS。

对于复杂的项目来说,需要复杂的配置,这时候分解配置文件为多个小的文件可以使得事情井井有条,以上面的例子来说,我们创建一个webpack.production.config.js的文件,在里面加上基本的配置,它和原始的webpack.config.js很像,如下

// webpack.production.config.js
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件
    output: {
        path: __dirname + "/build",
        filename: "bundle.js"
    },
    devtool: 'null', //注意修改了这里,这能大大压缩我们的打包代码
    devServer: {
        contentBase: "./public", //本地服务器所加载的页面所在的目录
        historyApiFallback: true, //不跳转
        inline: true,
        hot: true
    },
    module: {
        rules: [{
            test: /(\.jsx|\.js)$/,
            use: {
                loader: "babel-loader"
            },
            exclude: /node_modules/
        }, {
            test: /\.css$/,
            use: ExtractTextPlugin.extract({
                fallback: "style-loader",
                use: [{
                    loader: "css-loader",
                    options: {
                        modules: true
                    }
                }, {
                    loader: "postcss-loader"
                }],
            })
        }]
    },
    plugins: [
        new webpack.BannerPlugin('版权所有,翻版必究'),
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html" //new 一个这个插件的实例,并传入相关的参数
        }),
        new webpack.HotModuleReplacementPlugin() //热加载插件
    ],
};
//package.json
{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack",
    "server": "webpack-dev-server --open",
    "build": "NODE_ENV=production webpack --config ./webpack.production.config.js --progress"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
...
  },
  "dependencies": {
    "react": "^15.6.1",
    "react-dom": "^15.6.1"
  }
}
注意:如果是window电脑,build需要配置为"build": "set NODE_ENV=production && webpack --config ./webpack.production.config.js --progress".谢谢评论区简友提醒。

优化插件

webpack提供了一些在发布阶段非常有用的优化插件,它们大多来自于webpack社区,可以通过npm安装,通过以下插件可以完成产品发布阶段所需的功能

  • OccurenceOrderPlugin :为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
  • UglifyJsPlugin:压缩JS代码;
  • ExtractTextPlugin:分离CSS和JS文件

我们继续用例子来看看如何添加它们,OccurenceOrder 和 UglifyJS plugins 都是内置插件,你需要做的只是安装其它非内置插件

npm install --save-dev extract-text-webpack-plugin

在配置文件的plugins后引用它们

// webpack.production.config.js
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
    output: {
        path: __dirname + "/build",
        filename: "bundle.js"
    },
    devtool: 'none',
    devServer: {
        contentBase: "./public",//本地服务器所加载的页面所在的目录
        historyApiFallback: true,//不跳转
        inline: true,
        hot: true
    },
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true
                        }
                    }, {
                        loader: "postcss-loader"
                    }
                ]
            }
        ]
    },
    plugins: [
        new webpack.BannerPlugin('版权所有,翻版必究'),
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html"
        }),
        new webpack.optimize.OccurrenceOrderPlugin(),
        new webpack.optimize.UglifyJsPlugin(),
        new ExtractTextPlugin("style.css")
    ],
};

此时执行npm run build可以看见代码是被压缩后的

压缩后的代码

缓存

缓存无处不在,使用缓存的最好方法是保证你的文件名和文件内容是匹配的(内容改变,名称相应改变)

webpack可以把一个哈希值添加到打包的文件名中,使用方法如下,添加特殊的字符串混合体([name], [id] and [hash])到输出文件名前

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
..
    output: {
        path: __dirname + "/build",
        filename: "bundle-[hash].js"
    },
   ...
};

现在用户会有合理的缓存了。

带hash值的js名

去除build文件中的残余文件

添加了hash之后,会导致改变文件内容后重新打包时,文件名不同而内容越来越多,因此这里介绍另外一个很好用的插件clean-webpack-plugin

安装
cnpm install clean-webpack-plugin --save-dev

使用

引入clean-webpack-plugin插件后在配置文件的plugins中做相应配置即可:

const CleanWebpackPlugin = require("clean-webpack-plugin");
  plugins: [
    ...// 这里是之前配置的其它各种插件
    new CleanWebpackPlugin('build/*.*', {
      root: __dirname,
      verbose: true,
      dry: false
  })
  ]

关于clean-webpack-plugin的详细使用可参考这里

总结

其实这是一年前的文章了,趁周末重新运行和修改了一下,现在所有的代码都可以正常运行,所用webpack基于最新的webpack3.5.3。希望依旧能对你有帮助。

这是一篇好长的文章,谢谢你的耐心,能仔细看到了这里,大概半个月前我第一次自己一步步配置项目所需的Webpack后就一直想写一篇笔记做总结,几次动笔都不能让自己满意,总觉得写不清楚。其实关于Webpack本文讲述得仍不完全,不过相信你看完后已经进入Webpack的大门,能够更好的探索其它的关于Webpack的知识了。

欢迎大家在文后发表自己的观点讨论。

更新说明

2017-12-11更新,修改css module部分代码及示例图片,css module真的非常好用,希望大家都能用上。

2017年9月18日更新,添加了一个使用webpack配置多页应用的demo,可以点击此处查看

2017年8月13日更新,本文依据webpack3.5.3将文章涉及代码完全重写,所有代码都在Mac上正常运行过。希望依旧对你学习webpack有帮助。

2017年8月16号更新:
最近在Gitchat上将发起了一场关于webpack的分享,目的在于一起花最短的时间理解和学会webpack,感兴趣的童鞋可以微信扫描注册哈。
webpack从入门到工程实践

查看原文

哥本哈根的童话 关注了用户 · 2018-08-15

kejiacheng @kejiacheng

关注 553

认证与成就

  • 获得 0 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-08-15
个人主页被 107 人浏览