webpack 學習筆記:核心概念(下)

zhangbao發表於2020-09-26

Plugins

Loaders 是用來處理 webpack 預設不支援的型別檔案,將檔案轉換為 webpack 能正確認識的 module。Plugins 功能更加寬泛,用來做 Loaders 做不了的事情,比如:bundle 優化、資原始檔管理、環境變數注入等。

以 HtmlWebpackPlugin 這個外掛為例,它的作用是生成一個 html 檔案,將我們打包後的指令碼插入進去。

一、首先安裝依賴

$ npm install --save-dev html-webpack-plugin

二、在 webpack.config.js 中配置

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './path/to/my/entry/file.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'my-first-webpack.bundle.js'
    },
    module: {
        rules: [
            // See: https://webpack.js.org/loaders/raw-loader/
            { test: /\.txt$/, use: { loader: 'raw-loader', options: { esModule: false } } }
        ]
    },
    plugins: [
        // See: https://webpack.js.org/plugins/html-webpack-plugin/
        new HtmlWebpackPlugin()
    ]
};

webapck 外掛是通過 plugins 選項來配置新增的。上面我們指定打包過程中,使用 HtmlWebpackPlugin 這個外掛。

三、執行打包

$ npx webpack

Hash: 24ab0a25d3bb40a069db
Version: webpack 4.44.2
Time: 196ms
Built at: 2020-09-26 11:50:08 ├F10: AM┤
                     Asset       Size  Chunks             Chunk Names
                index.html  219 bytes          [emitted]  
my-first-webpack.bundle.js   1.64 KiB       0  [emitted]  main       
Entrypoint main = my-first-webpack.bundle.js
[0] ./path/to/my/entry/file.js 246 bytes {0} [built]
[1] ../package.json 375 bytes {0} [built]
[2] ./path/to/my/entry/test.txt 253 bytes {0} [built]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
Child HtmlWebpackCompiler:
     1 asset
    Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0
       1 module

在 dist/ 下能看到多出一個 index.html 檔案。

<!doctype html><html><head><meta charset="utf-8"><title>Webpack App</title><meta name="viewport" content="width=device-width,initial-scale=1"></head><body><script src="my-first-webpack.bundle.js"></script></body></html>

webpack 學習筆記:核心概念(下)

使用 HtmlWebpackPlugin 時,如果沒有顯式傳入模板檔案,外掛內部會使用預設的一套模板來注入 bundle 指令碼。當然也可以手動指定使用的模板檔案。

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    // ......
    plugins: [
        new HtmlWebpackPlugin({ template: './src/index.html' })
    ]
};

指定 src/index.html 為模板檔案,預設支援 ejs 模板語法藉助 lodash template 方法)。

接下來打包:

$ npx webpack

Hash: 7030b4ec93118804aa9d
Version: webpack 4.44.2
Time: 206ms
Built at: 2020-09-26 12:07:52 ├F10: PM┤
                     Asset       Size  Chunks             Chunk Names
                index.html  231 bytes          [emitted]
my-first-webpack.bundle.js   1.94 KiB       0  [emitted]  main
Entrypoint main = my-first-webpack.bundle.js

......
Child HtmlWebpackCompiler:
     1 asset
    Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0
    [0] ../node_modules/html-webpack-plugin/lib/loader.js!./src/index.html 476 bytes {0} [built]

依然打包成功了。

Mode

前面我們打包時,一直會看到下面的 warning。

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

表示在寫配置檔案時,要顯式設定 option 選項,否則預設使用的是 “production”,當然還可以取 “development” 和 “none” 值。它是用來針對不同環境,啟用 webpack 內建優化的。

module.exports = {
  mode: 'production' // 'production'(預設值) | 'development' | 'none'
};

“production”mode 前面已經看到過了,接下來再試試“development”mode。

/******/ (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, { 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 = "./path/to/my/entry/file.js");
/******/ })
/************************************************************************/
/******/ ({

/***/ "../package.json":
/*!***********************!*\
  !*** ../package.json ***!
  \***********************/
/*! exports provided: name, version, description, main, scripts, keywords, author, license, devDependencies, default */
/***/ (function(module) {

eval("module.exports = JSON.parse(\"{\\\"name\\\":\\\"webpack-demos\\\",\\\"version\\\":\\\"1.0.0\\\",\\\"description\\\":\\\"\\\",\\\"main\\\":\\\"index.js\\\",\\\"scripts\\\":{\\\"test\\\":\\\"echo \\\\\\\"Error: no test specified\\\\\\\" && exit 1\\\"},\\\"keywords\\\":[],\\\"author\\\":\\\"\\\",\\\"license\\\":\\\"ISC\\\",\\\"devDependencies\\\":{\\\"html-webpack-plugin\\\":\\\"^4.5.0\\\",\\\"raw-loader\\\":\\\"^4.0.1\\\",\\\"webpack\\\":\\\"^4.44.2\\\",\\\"webpack-cli\\\":\\\"^3.3.12\\\"}}\");\n\n//# sourceURL=webpack:///../package.json?");

/***/ }),

/***/ "./path/to/my/entry/file.js":
/*!**********************************!*\
  !*** ./path/to/my/entry/file.js ***!
  \**********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

eval("const pkg = __webpack_require__(/*! ../../../../../package.json */ \"../package.json\")\r\nconst p = __webpack_require__(/*! ./test.txt */ \"./path/to/my/entry/test.txt\")\r\n\r\nfunction say(what = 'Hello webpack') {\r\n    return `?(v${pkg.version}) ${what}`\r\n}\r\n\r\n// Sees: https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML\r\ndocument.body.insertAdjacentHTML('afterbegin', `\r\n    <h1>${say()}</h1>\r\n    ${\r\n        p.match(/[^\\r\\n]+/g).map(line => `<p>${line}</p>`).join('')\r\n    }\r\n`)\r\n\n\n//# sourceURL=webpack:///./path/to/my/entry/file.js?");

/***/ }),

/***/ "./path/to/my/entry/test.txt":
/*!***********************************!*\
  !*** ./path/to/my/entry/test.txt ***!
  \***********************************/
/*! no static exports found */
/***/ (function(module, exports) {

eval("module.exports = \"While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management and injection of environment variables.\\r\\n\\r\\nIn order to use a plugin, you need to require() it and add it to the plugins array. Most plugins are customizable through options. Since you can use a plugin multiple times in a configuration for different purposes, you need to create an instance of it by calling it with the new operator.\";\n\n//# sourceURL=webpack:///./path/to/my/entry/test.txt?");

/***/ })

/******/ });

最終打包的檔案中,包含詳細的註釋內容,註明當前那部分程式碼的檔案來源。

設定成“none”的話,表示不會對輸出做任何內建優化。

/******/ (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, { 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 = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

const pkg = __webpack_require__(1)
const p = __webpack_require__(2)

function say(what = 'Hello webpack') {
    return `?(v${pkg.version}) ${what}`
}

// Sees: https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
document.body.insertAdjacentHTML('afterbegin', `
    <h1>${say()}</h1>
    ${
        p.match(/[^\r\n]+/g).map(line => `<p>${line}</p>`).join('')
    }
`)


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

module.exports = JSON.parse("{\"name\":\"webpack-demos\",\"version\":\"1.0.0\",\"description\":\"\",\"main\":\"index.js\",\"scripts\":{\"test\":\"echo \\\"Error: no test specified\\\" && exit 1\"},\"keywords\":[],\"author\":\"\",\"license\":\"ISC\",\"devDependencies\":{\"html-webpack-plugin\":\"^4.5.0\",\"raw-loader\":\"^4.0.1\",\"webpack\":\"^4.44.2\",\"webpack-cli\":\"^3.3.12\"}}");

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

module.exports = "While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management and injection of environment variables.\r\n\r\nIn order to use a plugin, you need to require() it and add it to the plugins array. Most plugins are customizable through options. Since you can use a plugin multiple times in a configuration for different purposes, you need to create an instance of it by calling it with the new operator.";

/***/ })
/******/ ]);

Browser Compatibility

webpack 支援 IE9+ 以上的所有現代瀏覽器。如果需要支援舊版瀏覽器,那麼就需要載入 Promise polyfill 來支援 Promise API,以便支援 webpack 動態引入(Dynamic Imports) 特性。

Environment

webpack 需要 Node.js 8.x 及以上版本的支援。

(完)

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章