深入学习webpack(一)
深入学习webpack(一)
模块化的相关库和工具已经很多了,包括require.js、sea.js和一些工程化工具webpack、gulp、grant。那么我们该如何选择呢? 其实,我们只需要掌握了其中一种模块化的方案即可,因为这里最重要的是思想,而不是工具。在这些工具之中,webpack无疑是非常火的,并且在现在和未来也将大有可为,所以,这篇文章就好好的说说webpack!
参考文章:webpack官方api文档
https://doc.webpack-china.org/loaders/less-loader/
webpack安装的loader方式。
什么是webpack?
webpack是一款打包工具,其中的modules包括css、js、image等,通过webpack我们可以轻松的解决项目的依赖问题、模块化问题、后期的压缩测试等问题。
为什么使用webpack?
好用啊! 相比于requirejs、gulp、grunt, webpack的优势都是非常明显的!
开始
本地安装webpack
mkdir webpack-demo && cd webpack-demo npm init -y npm install --save-dev webpack
-
mkdir用于创建文件夹,&&标识当完成创建文件夹之后要做的指令。 cd 返回文件夹。
- npm init -y 即创建package.json,通过-y,我们可以创建默认的信息。
- --save-dev即将安装webpack记录在package.json中的dependicies中。
- 注意:我们建议本地安装webpack,而不是全局安装webpack,因为全局的webpack将会限制你所使用的版本,在某些情况下回出错。比如,安装loader和plugins可能会依赖最新的webpack,这样,使用本地安装webpack就是最好的选择了。
获取webpack相关命令
./node_modules/.bin/webpack --help # Shows a list of valid cli commands .\node_modules\.bin\webpack --help # For windows users webpack --help # If you installed webpack globally
- 即如果我们全局安装了webpack,直接webpack --help就可以获得相关的命令。
- 如果我们是本地安装,就需要进入node_modules下的bin文件夹中执行 webpack --help 命令, 否则会出错。(注意Mac和Windows下的区别)
创建app文件夹并创建index.js
function component () { var element = document.createElement('div'); /* lodash is required for the next line to work */ element.innerHTML = _.join(['Hello','webpack'], ' '); return element; } document.body.appendChild(component());
这段代码即将一个创建的div插入到body中去。
注意: 这里的_并没有定义,我们在后面会引入lodash,才能支持使用_。
为执行index.js,在根目录下创建index.html
<html> <head> <title>webpack 2 demo</title> <script src="https://unpkg.com/lodash@4.16.6"></script> </head> <body> <script src="app/index.js"></script> </body> </html>
OK! 到这里我们的这个项目就可以运行了,因为还没有做任何的配置,所以直接在浏览器中打开index.html可发现hello webpack。
总结一下可以发现,在两个js中,下面的js对上面的js的依赖的,lodash必须加载完成之后,index.js才能正常执行。但是,index.js对lodash的依赖又是隐式的,因为index.js并没有声明它需要lodash,而是直接认为_是全局中存在的,所以直接使用。
这样管理JavaScript存在下面的两个问题:
- 如果lodash不再存在后者两个js的顺序反了,那么这个应用将无法执行。
- 如果lodash存在但是没有被用到,即应用中不需要indexjs了,那么浏览器下载lodash就是多余的了。
为了解决上面两个问题,我们做出下面的改变。
为了将indexjs依赖的lodash和index打包在一起(形成明显的依赖关系),我们先安装lodash:
npm install --save lodash
在index.js中引入lodash
在index.js的最上方添加:
import _ from 'lodash';
注意: 这是引入lodash模块的_变量,使用import,这是一条语句,最后要添加分好。
index.html中只引入一个最后打包的文件
我们将index.html内容修改如下:
<html> <head> <title>webpack 2 demo</title> </head> <body> <script src="dist/bundle.js"></script> </body> </html>
即只保留了一个bundle.js,bundle.js是什么呢?实际上是最后我们通过webpack打包出来的文件,后面还会讲到。
到这里,我们就可以看到index.js是明确的依赖lodash的,并且将_绑定,这样,就不会造成全局污染了。
通过声明一个module(这里可以认为是index.js)所需要的依赖,webpack就可以利用这个信息来创建一个依赖图表,然后通过这个依赖图标将所需依赖按照正确的顺序打包到一个文件中。 对于没有用到的依赖将不会打包到文件中去。
现在,我们就可以来使用webpack运行项目了,并且把index.js当做入口文件,把bundle.js当做出口文件,且对于一个页面需要的所有代码都会被打包到这个出口文件中。
.\node_modules\.bin\webpack app/index.js dist/bundle.js
执行上述命令之后的结果如下:
C:\Users\Administrator\Desktop\webpack-demo>.\node_modules\.bin\webpack app/index.js dist/bundle.js Hash: df00524299afa8ed4535 Version: webpack 2.5.1 Time: 478ms Asset Size Chunks Chunk Names bundle.js 544 kB 0 [emitted] [big] main [0] ./~/.4.17.4@lodash/lodash.js 540 kB {0} [built] [1] ./app/index.js 276 bytes {0} [built] [2] (webpack)/buildin/global.js 509 bytes {0} [built] [3] (webpack)/buildin/module.js 517 bytes {0} [built]
然后在浏览器中打开index.html我们就可以看到成功的打包结果了,并且在根目录下产生了一个dist文件,其中包含了bundle.js。说明:dist是distribution,即分发的意思。
存在的问题: 原本就几十行的代码通过webpack的打包之后达到了惊人的17000多行,大小达到了500余k,这是不是会影响性能呢?
解释:并非如此,因为lodash就有17000行了。
这是bundle.js在开头添加的70行代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/******/ (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; /******/ /******/ // identity function for calling harmony imports with the correct context /******/ __webpack_require__.i = function(value) { return value; }; /******/ /******/ // 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 = 1); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) {
这是bundle.js在末尾添加的50行代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* 2 */ /***/ (function(module, exports) { var g; // This works in non-strict mode g = (function() { return this; })(); try { // This works if eval is allowed (see CSP) g = g || Function("return this")() || (1,eval)("this"); } catch(e) { // This works if the window reference is available if(typeof window === "object") g = window; } // g can still be undefined, but nothing to do about it... // We return undefined, instead of nothing here, so it's // easier to handle this case. if(!global) { ...} module.exports = g; /***/ }), /* 3 */ /***/ (function(module, exports) { module.exports = function(module) { if(!module.webpackPolyfill) { module.deprecate = function() {}; module.paths = []; // module.parent = undefined by default if(!module.children) module.children = []; Object.defineProperty(module, "loaded", { enumerable: true, get: function() { return module.l; } }); Object.defineProperty(module, "id", { enumerable: true, get: function() { return module.i; } }); module.webpackPolyfill = 1; } return module; }; /***/ }) /******/ ]);
后面会介绍他们的作用。
不难看出,这样做的好处是显而易见的,对于项目的依赖关系非常明显,不会出现浏览器下载多余的js文件情况、明确了每个项目的依赖关系,并且合并了两个js文件,在付出了一个120行代码的情况下减少了一个http请求,也许得不偿失,但是项目比较大时,所做的优化就会非常可观了。
使用ES2015modules
在index.js中我们使用了import来引入lodash模块,但是这个import是es6的语法,在如今的浏览器中大多都是不支持的,所以webpack会将之转化为es5,在bundle.js中多出来的代码就是做这个工作的。
值得注意的时:webpack不会修改你的除了import和export部分之外的代码。 如果你使用了ES2015的新特性,那么最好使用babel来转译为ES5的代码以获得更好的支持。
下面的代码就是index.js的代码,可以看到webpack对之进行了封装,并没有替换我们的核心代码:
/***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_lodash__ = __webpack_require__(0); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_lodash___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_lodash__); function component () { var element = document.createElement('div'); /* lodash is required for the next line to work */ element.innerHTML = __WEBPACK_IMPORTED_MODULE_0_lodash___default.a.join(['Hello','webpack'], ' '); return element; } document.body.appendChild(component()); /***/ }),
对webpack进行配置
对于这么一个简单的项目,我们在运行的时候就要写成:.\node_modules\.bin\webpack app/index.js dist/bundle.js。 那么对于更为复杂的项目,不难想象,我们是非常难管理的。
所以为了解决这个问题,我们可以使用一个webpack可以参考的配置文件来自动打包你得模块文件。在你创建了 webpack.config.js 文件之后,你就可以使用下面的简单配置来代替复杂的命令行操作了。
在根目录下添加如下的webpack.config.js文件:
var path = require('path'); module.exports = { entry: './app/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'); } };
说明:第一,path是node内置的,并且require也是node的用法,因为我们使用webpack是建立在使用node的基础上的。 第二,path.join()和path.resolve()是不同的,具体可见Stack Overflow。
至此,我们可以按照下面的方式来运行这个项目:
./node_modules/.bin/webpack --config webpack.config.js
这和我们之前的运行结果是一样的。其中,--config用于说明使用根目录下的配置文件,而这个配置文件就是 webpack.config.js。
另外,如果你使用的就是webpack.config.js(就是这个确定的文件名),并且也在根目录下,那么直接使用下面的命令就可以运行。
webpack
但是,如果你使用的文件名是 webpack.test.js,那么使用webpack就会报错,提示在根目录下不存在 webpack.config.js 这个配置文件,所以我们就必须使用下面的这个命令:
./node_modules/.bin/webpack --config webpack.test.js
这同样可以达到目的。
配置文件的灵活性非常高,它允许我们增加loader规则、plugins、resolve选项以及其他很多对bundles的控制。
通过npm使用webpack
上面所给的运行webpack的方式都比较复杂,我们可以通过在package.json中这样设置来简化操作:
{ ... "scripts": { "build": "webpack" }, ... }
这是package.json中的script选项,阮一峰的一篇文章中对此做了详细的解释,大家可以搜索看看。
通过这样的设置,我们就可以使用下面的命令运行webpack了。
npm run build
同样的,如果我们把webpack.config.js修改为webpack.test.js,我们可以在package.json中修该build为“./node_modules/.bin/webpack --config webpack.test.js”,最后通过 npm run build也可以成功运行。