webpack是一款模块加载器兼打包工具,它能把各种资源,例如JS(含JSX)、coffee、样式(含less/sass)、图片等都作为模块来使用和处理。官方介绍:A bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows to load parts for the application on demand. Through “loaders” modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, … and your custom stuff.

  • webpack is a bundler for modules. The main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset.
  • Performance:webpack uses async I/O and has multiple caching levels. This makes webpack fast and incredibly fast on incremental compilations.
  • Code Splitting:webpack allows you to split your codebase into multiple chunks. Chunks are loaded asynchronously at runtime. This reduces the initial loading time.
  • 术语“transpile”(此处我们翻译成“转码”)。这个单词是转换(transform)和编译(compile)的混搭。
  • Tree shaking 是一个术语,通常用来描述移除 JavaScript 上下文中无用代码这个过程,或者更准确的说是按需引用代码,它依赖于 ES2015 模块系统中 import/export 的静态结构特性。
  • Hot Module Replacement(热模块替换)。这是一个接口(module.hot.accept),它允许在运行时更新各种模块,而无需进行完全刷新。模块热替换(HMR - Hot Module Replacement)功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。
  • Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running without a page reload.
    You have two ways to enable Hot Module Replacement with the webpack-dev-server.
    (1) Specify –hot and –inline on the command line:$ webpack-dev-server –hot –inline。
    Meaning of the options: A、–hot: adds the HotModuleReplacementPlugin and switch the server to hot mode.B、embed the webpack-dev-server runtime into the bundle.
    (2) Modify webpack.config.js.
    A、add new webpack.HotModuleReplacementPlugin() to the plugins field
    B、add webpack/hot/dev-server and webpack-dev-server/client?http://localhost:8080 to the entry field
    eg:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
           // launch the dev server:$ webpack-dev-server   
    module.exports = {
    entry: [
    'webpack/hot/dev-server',
    'webpack-dev-server/client?http://localhost:8080',
    './index.js'
    ],
    output: {
    filename: 'bundle.js',
    publicPath: '/static/'
    },
    plugins: [
    new webpack.HotModuleReplacementPlugin()
    ],
    ......
    }
    1
    2
    3
    4
    output: {
    path: "/home/proj/public/assets", //path to where webpack will build your stuff
    publicPath: "/assets/" //path that will be considered when requiring your files
    }
    1
    2
    3
    4
    5
    // To enable requiring files without specifying the extension, you must add a resolve.extensions parameter specifying which files webpack searches for:
    resolve: {
    // you can now require('file') instead of require('file.coffee')
    extensions: ['', '.js', '.json', '.coffee']
    }
  • 虽然在大多数浏览器中都不支持import和export语句,但是webpack却能够提供支持。事实上,webpack在幕后会将代码“转译”,以便旧有浏览器可以执行。如果你检查dist/bundle.js,你可以看到webpack具体如何实现,这是独创精巧的设计!

  • 注意,webpack不会更改代码中除import和export语句以外的部分。如果你在使用其它ES2015特性,请确保你使用了一个像是Babel的转译器。
  • 通过声明模块所需的依赖,webpack 能够利用这些信息去构建依赖图表,然后使用图表生成一个优化过的,会以正确顺序执行的 bundle。
  • webpack只有单一的入口,其它的模块需要通过import、require、url等导入相关位置。
  • Webpack会分析入口文件,解析包含依赖关系的各个文件。这些文件(模块)都打包到bundle.js。Webpack会给每个模块分配一个唯一的id并通过这个id索引和访问模块。在页面启动时,会先执行entry.js中的代码,其它模块会在运行require的时候再执行。
  • webpack有一个智能解析器,几乎可以处理任何第三方库,无论它们的模块形式是CommonJS、AMD还是普通的JS文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    /******/ (function(modules) { // webpackBootstrap
    /******/ // The module cache
    /******/ var installedModules = {};

    /******/ // The require function
    /******/ function __webpack_require__(moduleId) {

    /******/ // Check if module is in cache
    /******/ if(installedModules[moduleId])
    /******/ return installedModules[moduleId].exports;

    /******/ // Create a new module (and put it into the cache)
    /******/ var module = installedModules[moduleId] = {
    /******/ exports: {},
    /******/ id: moduleId,
    /******/ loaded: false
    /******/ };

    /******/ // Execute the module function
    /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

    /******/ // Flag the module as loaded
    /******/ module.loaded = true;

    /******/ // Return the exports of the module
    /******/ return module.exports;
    /******/ }

    /******/ // expose the modules object (__webpack_modules__)
    /******/ __webpack_require__.m = modules;

    /******/ // expose the module cache
    /******/ __webpack_require__.c = installedModules;

    /******/ // __webpack_public_path__
    /******/ __webpack_require__.p = "";

    /******/ // Load entry module and return exports
    /******/ return __webpack_require__(0);
    /******/ })
    /************************************************************************/
    /******/ ([
    /* 0 */
    /***/ function(module, exports, __webpack_require__) {

    document.write(__webpack_require__(1));

    /***/ },
    /* 1 */
    /***/ function(module, exports) {

    moudle.exports = " it works from content.js";

    /***/ }
    /******/ ]);
  • Webpack本身只能处理JavaScript模块,如果要处理其他类型的文件,就需要使用loader进行转换。

  • Loader可以理解为是模块和资源的转换器,它本身是一个函数,接受源文件作为参数,返回转换的结果。这样,我们就可以通过require来加载任何类型的模块或文件,比如CoffeeScript、JSX、LESS或图片。
    Loader特性:Loader可以通过管道方式链式调用,每个loader可以把资源转换成任意格式并传递给下一个loader,但是最后一个loader必须返回JavaScript。Loader运行在node.js环境中,所以可以做任何可能的事情。多个loader可以用在同一个文件上并且被链式调用。链式调用时从右到左执行且loader之间用“!”来分割。
  • 我们要在页面中引入一个CSS文件style.css,首页将style.css也看成是一个模块,然后用css-loader来读取它,再用style-loader把它插入到页面中。eg:require(“!style!css!./style.css”); //载入 style.css document.write(require(“./content.js”));
  • 如果每次require CSS文件的时候都要写loader前缀,是一件很繁琐的事情。我们可以根据模块类型(扩展名)来自动绑定需要的loader这样写require(“./style.css”)。命令就是:webpack ./entry.js bundle.js –module-bind “css=style!css”。
  • 加载图片:一个url-loader来说,它会将样式中引用到的图片转为模块来处理。
  • Webpack在执行的时候,除了在命令行传入参数,还可以通过指定的配置文件来执行。默认情况下,会搜索当前目录的webpack.config.js文件,这个文件是一个node.js模块,返回一个json格式的配置信息对象,或者通过–config选项来指定配置文件。
  • Webpack配置项说明:
    entry:指定打包的入口文件,每有一个键值对,就是一个入口文件
    output:配置打包结果,path定义了输出的文件夹,filename则定义了打包结果文件的名称
    module:定义了对模块的处理逻辑,这里可以用loaders定义了一系列的加载器,以及一些正则。当需要加载的文件匹配test的正则时,就会调用后面的loader对文件进行处理,这正是webpack强大的原因。
  • 插件可以完成更多loader不能完成的功能。插件的使用一般是在webpack的配置信息plugins选项中指定。Webpack本身内置了一些常用的插件,还可以通过npm安装第三方插件。
  • 要使用某个插件,我们需要通过npm安装它,然后要做的就是在webpack配置中的plugins关键字部分添加该插件的一个实例(plugins是一个数组)。
  • expose-loader——-require(‘expose?$!jquery’):把$作为别名为jquery的变量暴露到全局上下文中。

    1
    2
    3
    4
    5
    6
    7
    /* 1 */
    /***/ function(module, exports, __webpack_require__) {

    /* WEBPACK VAR INJECTION */(function(global) {module.exports = global["$"] = __webpack_require__(2);
    /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

    /***/ }
  • TinyPNG — How does it work? This technique is called “quantization”. By reducing the number of colors, 24-bit PNG files can be converted to much smaller 8-bit indexed color images. All unnecessary metadata is stripped too. The result: better PNG files with 100% support for transparency.

  • 检查字符看是相等实际却不等的情况,encodeURIComponent(‘ dev ‘)的结果”%20dev%20”,一般是出现不可见字符空格导致。
  • Multiple entry files are allowed. It is useful for a multi-page app.
    1
    2
    3
    4
    // main1.js
    document.write('<h1>Hello World</h1>');
    // main2.js
    document.write('<h2>Hello Webpack</h2>');
1
2
3
4
5
6
<html>
<body>
<script src="bundle1.js"></script>
<script src="bundle2.js"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
// webpack.config.js
module.exports = {
entry: {
bundle1: './main1.js',
bundle2: './main2.js'
},
output: {
filename: '[name].js'
}
};
  • webpack –display-error-details//带上参数可以找出详细的错误信息
  • 由于 loader 仅在每个文件的基础上执行转换,而 插件(plugins) 更常用于(但不限于)在打包模块的 “compilation” 和 “chunk” 生命周期执行操作和自定义功能。插件目的在于解决 loader 无法实现的其他事。
  • The Manifest:Let’s step back for a second now and ask a more high-level question – how do these plugins know what files are being spit out? The answer is in the manifest webpack keeps to track how all your modules map to the output bundles. If you’re interested in managing webpack’s output in other ways, the manifest would be a good place to start.This data can be extracted into a json file for easy consumption using the WebpackManifestPlugin or ChunkManifestPlugin.
  • 你可能会感兴趣,webpack及其插件似乎“知道”应该哪些文件生成。答案是,通过manifest,webpack能够对「你的模块映射到输出bundle的过程」保持追踪。

    1
    2
    3
    4
    5
    {
    "bundle1.js": "bundle1-cc551b2f.js",
    "bundle2.js": "bundle2-cc6224e7.js",
    "init.[chunkhash:8].js.js": "init.405189cd.js"
    }
  • 在运行 webpack 时设置环境变量,并且使用 Node.js 的 process.env 来引用变量。NODE_ENV 变量通常被视为事实标准。使用 cross-env 包来跨平台设置(cross-platform-set)环境变量。

  • 环境变量的设置://注意这里单引号间多了个双引号
    var env = {
    ‘process.env.NODE_ENV’: ‘“production”‘
    }
    new webpack.DefinePlugin(env)
  • CSS会跟你的JavaScript打包在一起,并且在初始加载后,通过一个style标签注入样式,然后作用于页面。这里有一个缺点就是,你无法使用浏览器的能力,去异步且并行去加载 CSS。取而代之的是,你的页面需要等待整个 JavaScript 文件加载完,才能进行样式渲染。webpack 能够用 ExtractTextWebpackPlugin 帮助你将 CSS 单独打包,以解决以上问题。
  • 启动webpack-dev-server后,在目标文件夹中是看不到编译后的文件的,实时编译后的文件都保存到了内存当中。因此很多同学使用webpack-dev-server进行开发的时候都看不到编译后的文件。webpack-dev-server在webpack的watch基础上开启服务器。
  • 在webpack中实现HMR也很简单,只需要做两项配置:1、在webpack配置文件中添加HMR插件;2、在Webpack Dev Server中添加“hot”参数;
  • webpack可以把一个哈希值添加到打包的文件名中,添加特殊的字符串混合体([name], [id] and [hash])到输出文件名前。
  • 打包后的文件有时候你是不容易找到出错了的地方对应的源代码的位置的,Source Maps就是来帮我们解决这个问题的。
  • 定义环境变量NODE_ENV=production:在环境变量 NODE_ENV 等于 production 的时候UglifyJs会认为if语句里的是死代码在压缩代码时删掉。
  • CommonsChunkPlugin可以提取出多个代码块都依赖的模块形成一个单独的模块。要发挥CommonsChunkPlugin的作用还需要浏览器缓存机制的配合。在应用有多个页面的场景下提取出所有页面公共的代码减少单个页面的代码,在不同页面之间切换时所有页面公共的代码之前被加载过而不必重新加载。这个方法可以非常有效的提升应用性能。
  • 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.
  • 分析输出结果:webpack –json –profile > stats.json。这代表让webpack把构建结果以json输出并带上构建性能信息。会生产一个stats.json文件,再打开webpack analyze 上传这个文件开始分析。 webpack analyze:https://webpack.github.io/analyse/
  • “path”仅仅告诉Webpack结果存储在哪里,然而“publicPath”项则被许多Webpack的插件用于在生产模式下更新内嵌到css、html文件里的url值。
  • webpack在构建时,会静态解析(statically parse)代码中的require.ensure()。在其中任何被引用的依赖模块,或在回调函数中被require()的模块,都将被分离到一个新的chunk中。这个新的chunk会被生成为异步的bundle,由webpack通过jsonp来按需加载。
  • At first, you use require.ensure to define a split point.require.ensure tells Webpack that ./a.js should be separated from bundle.js and built into a single chunk file.Now Webpack takes care of the dependencies, output files and runtime stuff. You don’t have to put any redundancy into your index.html and webpack.config.js.On the surface, you won’t feel any differences. However, Webpack actually builds main.js and a.js into different chunks(bundle.js and 1.bundle.js), and loads 1.bundle.js from bundle.js when on demand.
  • If you want to use some global variables, and don’t want to include them in the Webpack bundle, you can enable externals field in webpack.config.js。
  • 运行webpack -p (也可以运行 webpack –optimize-minimize –define process.env.NODE_ENV=”production”,他们是等效的)。它会执行如下步骤:1、使用UglifyJsPlugin进行JS文件压缩。 2、设置NodeJS环境变量,触发某些package包,以不同的方式进行编译。DefinePlugin在原始的源码中执行查找和替换操作,在导入的代码中,任何出现process.env.NODE_ENV的地方都会被替换为”production”。因此,形如if (process.env.NODE_ENV !== ‘production’) console.log(‘…’)的代码就会等价于if (false) console.log(‘…’)并且最终通过UglifyJS等价替换掉。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    // 按需加载 require.ensure
    require.ensure(['./a'], function(require) {
    var content = require('./a');
    // todo
    });

    //打包后 bundle.js
    /******/ (function(modules) { // webpackBootstrap
    /******/ // install a JSONP callback for chunk loading
    /******/ var parentJsonpFunction = window["webpackJsonp"];
    /******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
    /******/ // add "moreModules" to the modules object,
    /******/ // then flag all "chunkIds" as loaded and fire callback
    /******/ var moduleId, chunkId, i = 0, callbacks = [];
    /******/ for(;i < chunkIds.length; i++) {
    /******/ chunkId = chunkIds[i];
    /******/ if(installedChunks[chunkId])
    /******/ callbacks.push.apply(callbacks, installedChunks[chunkId]);
    /******/ installedChunks[chunkId] = 0;
    /******/ }
    /******/ for(moduleId in moreModules) {
    /******/ modules[moduleId] = moreModules[moduleId];
    /******/ }
    /******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);
    /******/ while(callbacks.length)
    /******/ callbacks.shift().call(null, __webpack_require__);

    /******/ };

    /******/ // The module cache
    /******/ var installedModules = {};

    /******/ // object to store loaded and loading chunks
    /******/ // "0" means "already loaded"
    /******/ // Array means "loading", array contains callbacks
    /******/ var installedChunks = {
    /******/ 0:0
    /******/ };

    /******/ // The require function
    /******/ function __webpack_require__(moduleId) {

    /******/ // Check if module is in cache
    /******/ if(installedModules[moduleId])
    /******/ return installedModules[moduleId].exports;

    /******/ // Create a new module (and put it into the cache)
    /******/ var module = installedModules[moduleId] = {
    /******/ exports: {},
    /******/ id: moduleId,
    /******/ loaded: false
    /******/ };

    /******/ // Execute the module function
    /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

    /******/ // Flag the module as loaded
    /******/ module.loaded = true;

    /******/ // Return the exports of the module
    /******/ return module.exports;
    /******/ }

    /******/ // This file contains only the entry chunk.
    /******/ // The chunk loading function for additional chunks
    /******/ __webpack_require__.e = function requireEnsure(chunkId, callback) {
    /******/ // "0" is the signal for "already loaded"
    /******/ if(installedChunks[chunkId] === 0)
    /******/ return callback.call(null, __webpack_require__);

    /******/ // an array means "currently loading".
    /******/ if(installedChunks[chunkId] !== undefined) {
    /******/ installedChunks[chunkId].push(callback);
    /******/ } else {
    /******/ // start chunk loading
    /******/ installedChunks[chunkId] = [callback];
    /******/ var head = document.getElementsByTagName('head')[0];
    /******/ var script = document.createElement('script');
    /******/ script.type = 'text/javascript';
    /******/ script.charset = 'utf-8';
    /******/ script.async = true;

    /******/ script.src = __webpack_require__.p + "" + chunkId + ".bundle.js";
    /******/ head.appendChild(script);
    /******/ }
    /******/ };

    /******/ // expose the modules object (__webpack_modules__)
    /******/ __webpack_require__.m = modules;

    /******/ // expose the module cache
    /******/ __webpack_require__.c = installedModules;

    /******/ // __webpack_public_path__
    /******/ __webpack_require__.p = "";

    /******/ // Load entry module and return exports
    /******/ return __webpack_require__(0);
    /******/ })
    /************************************************************************/
    /******/ ([
    /* 0 */
    /***/ function(module, exports, __webpack_require__) {

    __webpack_require__.e/* require.ensure */(1, function(require) {
    var content = __webpack_require__(1);
    document.open();
    document.write('<h1>' + content + '</h1>');
    document.close();
    });

    /***/ }
    /******/ ]);
1
2
3
4
5
6
7
8
9
10
//打包后  1.bundle.js
webpackJsonp([1],[
/* 0 */,
/* 1 */
/***/ function(module, exports) {

module.exports = 'Hello World';

/***/ }
]);
  • 如何将您的bundle拆分成可以在之后异步下载的chunk。例如,这允许首先提供最低限度的引导bundle,并在稍后再异步地加载其他功能。webpack支持两种相似的技术实现此目的:使用import()(推荐,ECMAScript提案)和require.ensure()(遗留,webpack特定)。ES2015 loader规范定义了import()作为一种在运行时(runtime)动态载入ES2015模块的方法。 webpack把import()作为一个分离点(split-point),并把引入的模块作为一个单独的chunk。import()将模块名字作为参数并返回一个Promoise对象。import()在内部依赖于Promise。如果你想在老版本浏览器使用import(),请记得使用polyfill(例如es6-promise或promise-polyfill)来shim Promise。
  • new webpack.optimize.CommonsChunkPlugin(/ chunkName= /‘vendor’, / filename= /‘vendor.js’)
  • new ExtractTextPlugin(“[name]-[contenthash].css”)
  • 通过jsonp实现,并注意脚本的加载顺序:Demo10: Code splitting、Demo12: Common chunk(When multi scripts have common chunks, you can extract the common part into a separate file with CommonsChunkPlugin)、Demo13: Vendor chunk(You can also extract the vendor libraries from a script into a separate file with CommonsChunkPlugin)。
  • externals配置选项提供了「从输出的bundle中排除依赖」的方法。相反,所创建的bundle依赖于那些存在于用户环境(consumer’s environment)中的依赖。Exposing global variables:If you want to use some global variables, and don’t want to include them in the Webpack bundle, you can enable externals field in webpack.config.js。externals 中:key是require的包名,value是全局的变量。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ({
    0: function(...) {
    var jQuery = require(1);
    /* ... */
    },
    1: function(...) {
    // 很明显这里是把window.jQuery赋值给了module.exports
    // 因此我们便可以使用require来引入了。
    module.exports = jQuery;
    },
    /* ... */
    });
  • library和libraryTarget的使用场景:有些时候我们想要开发一个库,如lodash、underscore这些,这些库既可以用commonjs和amd的方式使用,也可以通过script标签的方式引入使用,目前很多库都是支持这几种使用方式的。

  • hash、chunkhash、contenthash。filename可以包含路径。
  • 下面这张来自webpack官网的图片,可以很清晰地说明module、entry、chunk三者的关系以及webpack如何实现热更新的: