webpack 2.6.1入门

来源:互联网 发布:招商银行网络金融部 编辑:程序博客网 时间:2024/05/16 10:55

开始跟着官网的documentation学习webpack的使用。

首先指出,官网中的tutorial的get_started章节里关于loaders的部分语法在webpack2.6.1中已经被弃用或者说更新。webpack2.6.1中的引入loaders都需要加上“-loader”后缀。如下图所示:

这里写图片描述

监视模式:
webpack可以监控所有文件,当静态文件改变时会自动重新编译和打包;监视模式可以在有文件改变和重新编译时调用上次缓存未改变的模块和输出文件,有效提高性能。

webpack --progress --colors --watch

程式开发伺服器:

相比webpack的watch模式,该方式不仅可以自动重新打包,而且可以自动更新浏览器页面。其原理是将网站的静态资源放到一个express的本地服务器上,其中http://localhost:8080及http://localhost:8080/webpack-dev-server映射到项目根目录下,而http://localhost:8080/webpack-dev-server/bundle映射到项目主页,静态资源更新后该页面也会自动更新。

使用前需要全局安装:

npm install -g webpack-dev-server

然后使用以下命令:

webpack-dev-server --progress --color

四个重要概念

  1. Entry:
    webpack会根据其所有应用的依赖关系生成一个依赖图,并根据依赖图将这些资源进行打包,而Entry就是这个依赖图的起始点。关键词为entry

    module.exports = {    entry: './yourOwnEntry.js'};
  2. Output:
    定义打包的静态资源的输出路径。关键词为output,其还具有子属性path和filename

    const path = require('path');module.exports = {      entry: './yourOwnEntry.js'      output: {            path: path.resolve(__dirname, 'dist'),  // __dirname为项目根目录            filename: 'my-first-webpack.bundle.js'      }};
  3. Loaders
    webpack将每个文件均看做一个模块,它只能解析原生的JS文件,不能解析.css/.html/.scss/.jpg等文件。因此,为了将这些资源正确的加入到项目依赖图中,需要引入关键词rules。rules定义了哪些文件需要被加入到依赖图中并打包,具有子属性test和use。

    const path = require('path');const config = {     entry: './path/to/my/entry/file.js',     output: {          path: path.resolve(__dirname, 'dist'),          filename: 'my-first-webpack.bundle.js'      },      module: {          rules: [ //webpack2.x中loaders被rule代替              {test: /\.(js|jsx)$/, use: 'babel-loader'}        ]      }};module.exports = config;
  4. Plugins:

    loaders只能将处理单一类型的文件,而plugins功能更加强大。通过使用plugins可以添加一些功能性的打包文件,并可以通过new创建实例供多次调用。


常用命令

--config  传入配置文件路径,默认为webpack.config.js和webpackfile.js;--entry  定义打包的入口点文件--watch  监听模式,可以在修改模块后自动重新打包;--progress  显示百分比格式的编译进程--output-path  输出路径--output-filename  输出文件名--cache  使能缓存(watch模式下自动开启)--hot  使能模块热替换--color/ --colors  开启/关闭console中的颜色;--display-reasons  显示模块被包含到输出中的原因--module-bind  绑定一个加载器(--module-bind 'css=style-loader!css-loader')

css-loader使webpack可以处理css文件并生成相应模块,而style-loader相当于在head中插入一个style标签并将处理后的css文件引入到HTML中;


浅析打包过程

1、仅新建一个hello.js文件,不进行外部引用

可以看到生成的hello.bundle.js中由两部分组成;

// 第一部分/******/ (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] = {/******/            i: moduleId,/******/            l: false,/******/            exports: {}/******/        };/******//******/        // Execute the module function/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);/******//******/        // Flag the module as loaded/******/        module.l = 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;/******//******/    // define getter function for harmony exports/******/    __webpack_require__.d = function(exports, name, getter) {/******/        if(!__webpack_require__.o(exports, name)) {/******/            Object.defineProperty(exports, name, {/******/                configurable: false,/******/                enumerable: true,/******/                get: getter/******/            });/******/        }/******/    };/******//******/    // getDefaultExport function for compatibility with non-harmony modules/******/    __webpack_require__.n = function(module) {/******/        var getter = module && module.__esModule ?/******/            function getDefault() { return module['default']; } :/******/            function getModuleExports() { return module; };/******/        __webpack_require__.d(getter, 'a', getter);/******/        return getter;/******/    };/******//******/    // Object.prototype.hasOwnProperty.call/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };/******//******/    // __webpack_public_path__/******/    __webpack_require__.p = "";/******//******/    // Load entry module and return exports/******/    return __webpack_require__(__webpack_require__.s = 0);/******/ })

分析:首先会创建一个installedModules对象用于模块缓存;
接下来,模块中使用的require()函数是通过__webpack_require__(moduleId)定义,通过打包时分配的模块ID进行查找并引入相应模块(这种处理方式与CommonJS很相像,在webpack中不管是用CommonJS、AMD或者是ES6的模块化方法,webpack都会转换为自身的__webpack_require__(moduleId)进行处理);这里首先会在installedModules中查找缓存中是否存在相应模块,存在则直接返回,否则根据传入moduleID新建一个模块;创建新模块时需要定义相应的模块属性:i为模块ID,l则是一个标示模块是否加载的标志位;最后导出新建的模块;

第二部分如下:

/******/ ([/* 0 *//***/ (function(module, exports) {function hello (str) {    console.log(str);}/***/ })/******/ ]);

可以看到,打包后的模块被分配了一个ID号用于标识,方便后期依赖引用;另外,模块的代码也被包含在内;

// world.jsfunction world () {    return 'world';}

当在hello.js中require(‘./world.js’)后重新打包,可以看到hello.js依赖的模块也被打包
这里写图片描述

此时bundle.js中的第二部分变为了

/******/ ([/* 0 *//***/ (function(module, exports, __webpack_require__) {__webpack_require__(1)function hello (str) {    console.log(str);}/***/ }),/* 1 *//***/ (function(module, exports) {function world () {    return 'world';}/***/ })/******/ ]);

windows中路径分隔符使用‘\’,并且webpack更倾向于使用绝对地址,因此不要使用__dirname + ‘/a/b’,可以先通过require(‘path’)引入path模块,然后使用path.resolve(__dirname, '/a/b)或者path.join(__dirname, 'a', 'b');

为了更加便捷的使用webpack,可以在package.json文件中定义特定的格式命令指定相应的命令行参数,但是更便捷的方式是使用配置文件,webpack.config.js中使用如下配置:

var path = require('path')module.exports = {    entry: path.resolve(__dirname, 'src/script/main.js'),    output: {        path: path.resolve(__dirname, 'dist/js'),        filename: 'bundle.js'    },    module: {        rules: [{            test: /\.css$/,            use: [{                loader: 'style-loader'            }, {                loader: 'css-loader'            }]        }, {            test: /(\.jsx|\.js)$/,            loader: 'babel-loader'        }]    }}

继续上边的例子,引入css文件

// main.jsvar test = require('./sub1')require('../style/main.css');function helloWorld () {    console.log('hello');}helloWorld();console.log(test.id);console.log(test.name);// sub1.jsmodule.exports = {    id: 1,    name: 'fn'}// main.csshtml, body {    margin: 0;    padding: 0;}body {    background-color: #bfbfbf;}h1 {    color: green;}

重新打包后效果如下:
这里写图片描述


打包图片文件

webpack中的图片文件包括CSS中的背景图片,模板中的图片文件,顶层的HTML文件中的图片文件等;

对于CSS文件中的背景图片,可以使用file-loader(还有其他的可以用于图片打包的loader,比如url-loader使用与file-loader类似,但是可以将图片转为data URL格式;image-webpack-loader提供了各种关于图片压缩优化的options;raw-loader可以加载utf-8编码的文件的原始内容);
模板中直接使用相对地址会报错,建议尽量使用上线时的绝对地址,或者在模板中使用${require(url)}进行正确解析;


Hot Module Replacement

HMR是webpack提供的可以在不进行全部重载的情况下在runtime进行模块更新的插件;此处参照官网简要分析下其工作原理。

应用程序
当一个应用程序发生更新时,应用程序首先询问HMR runtime检测更新,然后运行环境会异步加载并且更新这些模块,然后通知应用程序更新所需的资源已经就绪;应用程序收到消息后如果决定应用这些更新则会告知runtime并进行同步更新(更新所需资源已就绪,所以同步更新所需时间较短);

编译器
当发生模块更新时,编译器首先检测到更新,它会发出相应的更新信号,其中包括更新后的manifest文件(JSON格式)和多个更新的chunks(前边htmlWebpackPlugin中提到的多入口中的a.js/b.js/c.js都是chunk,每个chunk是个独立的JS文件);

manifest中包括了更新后的hash以及一个包含更新块的列表,每个chunk中是更新后的代码或一个表示模块已移除的标志位;如下图可以看到,每次编译后都有一个唯一的hash相对应;当修改src目录下的module时,对应的hash值就会改变;另外,webpack会把module ID和Chunk ID存储在内存中,并且每次更新前后需要保持module ID和Chunk ID的对应关系;

这里写图片描述

重点内容
模块中引入了HMR相关的代码时,它才会受到HMR的影响;模块通过HMR接口接收到更新的要求,然后可以自定义一些更新的操作,具体可以参考HMR API;
这里写图片描述

通常,引入了HMR的模块具有module.hot属性,可以反过来用于检测HMR接口是否可用;引入了HMR的模块一般需要有相应的handler定义模块更新后的响应方式,否则其可能会像事件冒泡一样导致相应的依赖都进行重载;当然并不强制要求为每个模块设置一个handler;

运行环境
runtime支持check和apply两个相应的方法。

check方法发送一个http请求去更新manifest。如果请求失败,就表示目前没有更新;如果请求成功则表示当前有更新。webpack会比较已经加载的chunks和更新后的chunks,然后根据manifest加载发生更新的模块;当所有需要更新的chunks已经就绪时,runtime进入ready状态;

apply的作用是应用这些更新。它会将所有更新的模块标记为不可用,每个模块需要在当前模块或者其父级模块中设置对应的handler,否则可能导致整个应用不可用;

之后,标记为invalid的模块通过module.hot.disposed Handler进行处理并卸载掉更新前的对应模块;接着,对应的hash被更新并调用所有的module.hot.accept Handler;
现在runtime进行到idle状态等待下一次更新;


参考文献

1、http://webpack.github.io/docs/troubleshooting.html
2、https://webpack.js.org/concepts/hot-module-replacement/
3、HMR API: https://webpack.js.org/api/hot-module-replacement/

原创粉丝点击