Webpack 原理與實踐之如何使用 Webpack 實現(xiàn)模塊化打包?
本文轉載自微信公眾號「前端萬有引力」,作者一川。轉載本文請聯(lián)系前端萬有引力公眾號。
寫在前面
我們知道當前生產(chǎn)中主流的模塊化打包工具有Webpack、Parcel和Rollup。作為模塊化打包工具,它們基本的特點有:
- 能夠將散落的模塊打包在一起
- 能夠編譯轉換代碼中的新特性,使得可以兼容各種生產(chǎn)環(huán)境
對于主流的webpack而言,webpack作為一個模塊打包工具,自身就可以實現(xiàn)模塊化代碼打包的問題,通過webpack可以將零散的JS代碼打包到一個JS文件中。對于有環(huán)境兼容性問題的代碼,webpack可以在代碼打包過程中通過loader機制對其實現(xiàn)編譯轉換,然后再進行打包。對于不同類型的前端模塊,webpack支持在js中以模塊化的方式載入任意類型的資源文件,例如:可以通過webpack實現(xiàn)在JS中加載CSS文件,被加載的CSS文件會以style標簽的方式工具。
webpack還具備代碼拆分的能力,能夠將應用中所有的模塊按需分塊打包,不用擔心全部代碼打包到一起,產(chǎn)生單個文件過大,導致加載慢的問題。這種模塊按需分塊打包非常適合大型web項目。
Webpack上路
Webpack作為主流的前端模塊打包器,提供了一整套前端項目模塊化方案,而不僅僅局限于JS的模塊化,可以實現(xiàn)對前端項目開發(fā)過程中涉及到的資源進行模塊化。
我們知道ES Modules中制定的html中使用script導入js模塊,需要設定type="module",用來區(qū)分加載的是普通JS腳本還是一個模塊。對于支持ES Modules的瀏覽器可以直接使用,但是對于不支持的瀏覽器就會報錯,因此需要使用webpack模塊打包工具避免報錯。
- <script src="./index.js" type="module"></script>
- // heading.js
- export default function(){
- const element = document.createElement("div");
- element.textContent = "hello yichuan";
- element.addEventListener("click",()=>{
- console.log("hello onechuan");
- return element
- })
- }
- // index.js
- import createHeader from "./heading.js";
- document.body.append(createHeader())
對于,我們需要先安裝webpack的核心模塊和cli程序,用于在命令行中調用webpack。npx是npm5.2以后新增的一個命令,可以更方便地執(zhí)行遠程模塊或者項目node_modules中的cli程序。
- $ npm init --yes
- $ npm i webpack webpack-cli -D
- $ npx webpack --version
- $ npx webpack
通過執(zhí)行npx webpack實現(xiàn)自動化打包,webpack會默認從src/index.js文件開始打包,打包完畢后會在根目錄下生產(chǎn)dist目錄,所有打包文件會在dist目錄。
- |--dist
- |--main.js
- (()=>{"use strict";document.body.append(function(){const e=document.createElement("div");e.textContent="hello yichuan",e.addEventListener("click",(()=>(console.log("hello onechuan"),e)))}())})();
webpack4.0以后的版本支持零配置的方式直接啟動打包,整個過程會按照約定將src/index.js文件作為打包入口,最終打包的結果會存放到dis/main.js中。但是,很多時候webpack的默認規(guī)則并能滿足我們實際需求,對此我們需要對webpack進行自定義配置。
webpack支持在項目中創(chuàng)建webpack.config.js文件進行自定義打包配置,由于webpack是運行在node.js環(huán)境,對應應該遵守CommonJS規(guī)則進行導入導出。
- |--backpack
- |--src
- |--heading.js
- |--main.js
- |--index.html
- |--package.json
- |--webpack.config.js-------webpack配置文件
webpack自定義的配置文件:
- // webpack.config.js
- const path = require("path");
- module.exports = {
- entry:"./src/index.js",//打包入口
- output:{//打包出口
- filename:"bundle.js",//命名打包后的文件
- path:path.join(__dirname,"output")
- },
- // 打包模式
- mode:"none"
- }
執(zhí)行打包命令后會在項目根目錄下自動生成打包后的文件目錄,我們看到下面是執(zhí)行打包命令后生成的文件,生成的是一個立即執(zhí)行函數(shù)。
- /******/ (function(modules) { // webpackBootstrap 函數(shù)
- /******/ // 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, { enumerable: true, get: getter });
- /******/ }
- /******/ };
- /******/
- /******/ // define __esModule on exports
- /******/ __webpack_require__.r = function(exports) {
- /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
- /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
- /******/ }
- /******/ Object.defineProperty(exports, '__esModule', { value: true });
- /******/ };
- /******/
- /******/ // create a fake namespace object
- /******/ // mode & 1: value is a module id, require it
- /******/ // mode & 2: merge all properties of value into the ns
- /******/ // mode & 4: return value when already ns object
- /******/ // mode & 8|1: behave like require
- /******/ __webpack_require__.t = function(value, mode) {
- /******/ if(mode & 1) value = __webpack_require__(value);
- /******/ if(mode & 8) return value;
- /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
- /******/ var ns = Object.create(null);
- /******/ __webpack_require__.r(ns);
- /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
- /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
- /******/ return ns;
- /******/ };
- /******/
- /******/ // 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 = "./src/index.js");
- /******/ })
- /************************************************************************/
- /******/ ({
- /***/ "./src/heading.js":
- /*!************************!*\
- !*** ./src/heading.js ***!
- \************************/
- /*! exports provided: default */
- /***/ (function(module, __webpack_exports__, __webpack_require__) {
- "use strict";
- eval("__webpack_require__.r(__webpack_exports__);\n// heading.js\n/* harmony default export */ __webpack_exports__[\"default\"] = (function(){\n const element = document.createElement(\"div\");\n element.textContent = \"hello yichuan\";\n element.addEventListener(\"click\",()=>{\n console.log(\"hello onechuan\");\n return element\n })\n});\n\n//# sourceURL=webpack:///./src/heading.js?");
- /***/ }),
- /***/ "./src/index.js":
- /*!**********************!*\
- !*** ./src/index.js ***!
- \**********************/
- /*! no exports provided */
- /***/ (function(module, __webpack_exports__, __webpack_require__) {
- "use strict";
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _heading_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./heading.js */ \"./src/heading.js\");\n// index.js\n\ndocument.body.append(Object(_heading_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])())\n\n//# sourceURL=webpack:///./src/index.js?");
- /***/ })
- /******/ });
在上面打包之后的文件中,src目錄中的每個模塊對應打包后文件中的一個函數(shù),從而實現(xiàn)模塊的私有作用域,在此文件中還掛載了一些其他的數(shù)據(jù)和工具函數(shù)。
寫在后面
這篇文章主要寫了webpack上路使用,對于webpack的基本使用并不是很復雜,特別是在webpack4.0后,很多配置都已經(jīng)被簡化。在這種配置并不復雜的前提下,開發(fā)人員對它的掌握程度,主要體現(xiàn)在是否能夠理解它的工作機制和原理。