看懂前端脚手架你须要这篇webpack

it's not webpack step by step(webpack2)

19年目标:消灭英语!我新开了一个公众号记录一个程序员学英语的历程html

有提高英语诉求的小伙伴能够关注公众号:csenglish 程序员学英语,天天花10分钟交做业,跟我一块儿学英语吧vue


分割webpack配置文件的多种方法

###(一)react

将你的配置信息写到多个分散的文件中去,而后在执行webpack的时候利用--config参数指定要加载的配置文件,配置文件利用moduleimports导出。你能够在webpack/react-starter 看到是使用这种发方法的。webpack

// webpack 配置文件

|-- webpack-dev-server.config.js
|-- webpack-hot-dev-server.config.js
|-- webpack-production.config.js
|-- webpack.config.js

复制代码
// npm 命令

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev-server": "webpack-dev-server --config webpack-dev-server.config.js --progress --colors --port 2992 --inline",
    "hot-dev-server": "webpack-dev-server --config webpack-hot-dev-server.config.js --hot --progress --colors --port 2992 --inline",
    "build": "webpack --config webpack-production.config.js --progress --profile --colors"
  },
复制代码

(二)

调用第三方的webpack工具,使用其集成的api,方便进行webpack配置。HenrikJoreteg/hjs-webpack 这个repo就是这么作的。git

var getConfig = require('hjs-webpack')


module.exports = getConfig({
  // entry point for the app
  in: 'src/app.js',

  // Name or full path of output directory
  // commonly named `www` or `public`. This
  // is where your fully static site should
  // end up for simple deployment.
  out: 'public',

  // This will destroy and re-create your
  // `out` folder before building so you always
  // get a fresh folder. Usually you want this
  // but since it's destructive we make it // false by default clearBeforeBuild: true }) 复制代码

(三) Scalable webpack configurations

ones that can be reused and combined with other partial configurations程序员

在单个配置文件中维护配置,可是区分好条件分支。调用不一样的npm命令时候设置不一样的环境变量,而后在分支中匹配,返回咱们须要的配置文件。github

这样作的好处能够在一个文件中管理不一样npm操做的逻辑,而且能够共用相同的配置。webpack-merge这个模块能够起到合并配置的做用。web

const parts = require('./webpack-config/parts');

switch(process.env.npm_lifecycle_event) {
  case 'build': 
    config = merge(common, 
      parts.clean(PATHS.build),
      parts.setupSourceMapForBuild(),
      parts.setupCSS(PATHS.app),
      parts.extractBundle({
        name: 'vendor',
        entries: ['react', 'vue', 'vuex']
      }),
      parts.setFreeVariable('process.env.NODE_ENV', 'production'),
      parts.minify()
      );
    break;
  default: 
    config = merge(common, 
      parts.setupSourceMapForDev(),
      parts.devServer(), 
      parts.setupCSS(PATHS.app));
}
复制代码
// minify example
exports.minify = function () {
  return {
    plugins: [
      new webpack.optimize.UglifyJsPlugin({
        compress: {
          warnings: false,
          drop_console: true
        },
        comments: false,
        beautify: false
      })
    ]
  }
}
复制代码

开发环境下的自动刷新

webpack-dev-server

webpack-dev-server在webpack的watch基础上开启服务器。vuex

webpack-dev-server是运行在内存中的开发服务器,支持高级webpack特性hot module replacement。这对于react vue这种组件化开发是很方便的。npm

使用webpack-dev-server命令开启服务器,配合HMR及能够实现代码更改浏览器局部刷新的能力。

hot module replacement

Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running without a page reload. 当应用在运行期间hmr机制可以修改、添加、或者移除相应的模块,而不使整个页面刷新。

hmr机制适用于单页应用。

要实现hmr机制,须要配合webpack-dev-server服务器,这个服务器自己就实现了监察watch文件改动的能力,再开启HMR选项,就添加了watch模块变化的能力。这是HMR机制能生效的基础。

从webpack编译器角度

每次修改一个模块的时候,webpack会生成两部分,一个是manifest.json,另外一部分是关于此次模块更新编译完成的chunks。manifest.json中存着的是chunk更改先后的hash值。

从编译器webpack的角度来说提供了hmr的原材料。供后续使用。

从模块的角度

模块发生变化时,webpack会生成以前讲过的两部分基础文件,可是什么时候将变化后的模块应用到app中去?这里就须要在应用代码中编写handler去接受到模块变化信息。可是不能在全部模块中编写handler吧?这里就用到了消息冒泡机制。

如图A.js、C.js没有相关hmr代码,B.js有相关hmr代码,若是c模块发生了变化,c模块没有hmr,那么就会冒泡到a、b模块。b模块捕捉到了消息,hmr运行时会相应的执行一些操做,而a.js捕捉不到信息,会冒泡到entry.js,而一旦有消息冒泡的入口块,这就表明本次hmr失败了,hmr会降级进行整个页面的reload。

从HMR运行时的角度

HMR运行时是一些相关的操做api,运行时支持两个方法: checkapply

check发起 HTTP 请求去获取更新的 manifest,以及一些更新事后的chunk。

环境变量的设置

var env = {
	'process.env.NODE_ENV': '"production"'
}
new webpack.DefinePlugin(env)
复制代码

注意这里单引号间多了个双引号 why?

以及webpack.DefinePlugin插件的原理?

开发的时候会想写不少只在开发环境出现的代码,好比接口mock等,在build命令后这些代码不会存在。

这对框架或者插件、组件的开发是颇有帮助的。vue,react等都会这么作。能够在这些框架的dev模式提供不少有用的提示信息。

打包文件分割

为什么要进行打包文件分割?

对于一个单页应用项目来讲,有分为业务代码和第三方代码,业务代码会频繁改动,而第三方代码通常来说变更的次数较少,若是每次修改业务代码都须要用户将整个js文件都从新下载一遍,对于加载性能来说是不可取的,因此通常而言咱们会将代码分为业务代码和第三方代码分别进行打包,虽然多了一个请求的文件,增长了一些网络开销,可是相比于浏览器能将文件进行缓存而言,这些开销是微不足道的。

咱们在entry中定义了app入口,相应的业务逻辑都封装在这个入口文件里,若是咱们想要第三方代码独立出来,就要再增长一个入口,咱们习惯使用vendor这个命名。

// app.js

require('vue');
require('vuex');
复制代码
// webpack.config.js


entry: {
    app: 'app/app.js',
    vendor: ['vue', 'vuex'],
  },
复制代码

vendor入口的传参是以一个数组的形式传递的,这是一种很是方便的注入多个依赖的方式,而且能把多个依赖一块儿打包到一个chunk中。并且不用手动的建立真实存在的入口文件。

这至关于:

// vendor.js

require('vue');
require('vuex');

// app.js

require('vue');
require('vuex');
复制代码
// webpack.config.js


entry: {
    app: 'app/app.js',
    vendor: 'app/vendor.js',
  },
复制代码

可是这样作只是声明了一个vendor入口而已,对于app这个入口来讲,打包完成的文件仍是会有vue和vuex依赖,而新增的入口vendor打包完成的文件也有了vue和vuex两个依赖。模块依赖关系以下图所示。

这里的A能够表明vue依赖,最后生成的打包文件是两个平行关系的文件,且都包含vue的依赖。

此时须要引入CommonsChunkPlugin插件

This is a pretty complex plugin. It fundamentally allows us to extract all the common modules from different bundles and add them to the common bundle. If a common bundle does not exist, then it creates a new one.

这是个至关复杂的插件,他的基础功能是容许咱们从不一样的打包文件中抽离出相同的模块,而后将这些模块加到公共打包文件中。若是公共打包文件不存在,则新增一个。同时这个插件也会将运行时(runtime)转移到公共chunk打包文件中。

plugins: [
  new webpack.optimize.CommonsChunkPlugin({
    names: ['vendor', 'manifest']
  })
]
复制代码

这里的name能够选择已经存在的块,这里就选择了vendor块,由于咱们原本就是将vendor块当作管理第三方代码的入口的。

而names传入一个数组,数组里包含两个trunk name,表示CommonsChunkPlugin插件会执行两次这个方法,第一次将公共的第三方代码抽离移到vendor的块中,这个过程以前也讲过会将运行时runtime也转移到vendor块中,第二次执行则是将运行时runtime抽离出来转移到manifest块中。这步操做解决了缓存问题。

这样处理,最后会生成3个打包文件chunk,app.js是业务代码,vendor则是公共的第三方代码,manifest.js则是运行时。

chunk type 块的类型大揭秘

webpack1.0官网介绍中的chunk类型读起来及其拗口chunk type, 因此我这里解读一下。

chunk是webpack中最基本的概念之一,且chunk经常会和entry弄混淆。在「打包文件分割部分」咱们定义了两个入口entry point -- app和vendor,而经过一些配置,webpack会生成最后的一些打包文件,在这个例子中最后生成的文件有app.js 、 vendor.js 、 manifest.js。这些文件便被称为块chunk

entry & chunk 能够简单的理解为一个入口、一个出口

在官方1.0文档中webpack的chunk类型分为三种:

  1. entry chunk 入口块
  2. normal chunk 普通块
  3. initial chunk 初始块

entry chunk 入口块

entry chunk 入口块不能由字面意思理解为由入口文件编译获得的文件,由官网介绍

An entry chunk contains the runtime plus a bunch of modules

能够理解为包含runtime运行时的块能够称为entry chunk,一旦本来存在运行时(runtime)的entry chunk失去了运行时,这个块就会转而变成initial chunk

normal chunk 普通块

A normal chunk contains no runtime. It only contains a bunch of modules.

普通块不包含运行时runtime,只包含一系列模块。可是在应用运行时,普通块能够动态的进行加载。一般会以jsonp的包装方式进行加载。而code splitting主要使用的就是普通块。

initial chunk 初始块

An initial chunk is a normal chunk.

官方对initial chunk的定义很是简单,初始块就是普通块,跟普通块相同的是一样不包含运行时runtime,不一样的是初始块是计算在初始加载过程时间内的。在介绍入口块entry chunk的时候也介绍过,一旦入口块失去了运行时,就会变成初始块。这个转变常常由CommonsChunkPlugin插件实现。

例子解释

仍是拿「打包文件分割」的代码作例子,

// app.js

require('vue');
require('vuex');
复制代码
// webpack.config.js


entry: {
    app: 'app/app.js',
    vendor: ['vue', 'vuex'],
  },
复制代码

没有使用CommonsChunkPlugin插件以前,两个entry分别被打包成两个chunk,而这两个chunk每一个都包含了运行时,此时被称为entry chunk入口块。

而一旦使用了CommonsChunkPlugin插件,运行时runtime最终被转移到了manifest.js文件,此时最终打包生成的三个chunkapp.js 、 vendor.js 、 manifest.js,app.js、vendor.js失去了runtime就由入口块变成初始块。

code splitting

前文有讲到将依赖分割开来有助于浏览器缓存,提升用户加载速度,可是当业务复杂度增长,代码量大始终是一个问题。这时候就须要normal chunk普通块的动态加载能力了。

It allows you to split your code into various bundles which you can then load on demand — like when a user navigates to a matching route, or on an event from the user. code splitting 容许咱们将代码分割到能够按需加载的不一样的打包文件中,当用户导航到对应的路由上时,或者是用户触发一个事件时,异步加载相应的代码。

咱们须要在业务逻辑中手动添加一些分割点,标明此处事件逻辑以后进行代码块的异步加载。

// test
window.addEventListener('click', function () {
  require.ensure(['vue', 'vuex'], function (require) {

  })  
})
复制代码

这段代码代表当用户点击时,异步请求一个js文件,这个文件中包含该有vue vuex的依赖。

打包后会根据手动分割点的信息生成一个打包文件,就是图中第一行0开头的文件。这个文件也就是异步加载的文件。

下面是以前的一个vue项目,采用code splitting将几个路由抽离出来异步加载以后,文件由212kb减小到了137kb,一样样式文件也由58kb减小到了7kb。对于首屏渲染来讲,性能是会增长很多的。

有须要交流的能够联系我微博 达达的暹罗猫

参考:

相关文章
相关标签/搜索