Webpack原理簡單講解
webpack是JavaScript應用程式的靜態模組打包器,webpack處理應用程式時,將遞迴構建一個依賴關係圖,依賴圖映射了專案中需要的每一個模組,並打包生成一個或多個bundle.
當在命令列執行指令 webpack 的時候,webpack將預設從當前目錄下查詢 webpack.config.js 檔案
webpack的配置(webpack.config.js)中有四個核心概念需要理解:
- 入口(entry)
- 輸出(output)
- loader
- 外掛(plugins)
入口(entry)
entry屬性將告訴webpack從哪個模組開始構建依賴圖,並計算出所有這個模組直接或間接依賴的模組.
entry為String或Array時,entry輸出的Chunk的名稱將預設是main
entry為Object時,webpack將輸出多個Chunk,Chunk的名稱將會是key
輸出(output)
output屬性將告訴webpack最終打包檔案的輸出路徑,以及如何命名打包檔案.
const path = require('path'); module.exports = { entry: './src/index.js', // ./dist/main.js output: { // 檔名字可以直接設定 filename: 'my-first-webpack.bundle.js', // 也可以用過變數設定 // id - Chunk的唯一標識,從0開始 // name - Chunk的名稱 // hash - Chunk的唯一標識(即id)的hash值 // chunkhash - Chunk內容的hash值 filename: '[id].[name].[hash].[chunkhash].js', // 配置輸出檔案的存放目錄 // __dirname是執行命令列時所在路徑 path: path.resolve(__dirname, 'dist'), } }; 複製程式碼
loader
module屬性配置如何處理模組,其中rules配置模組的讀取和解析規則,通常用來配置Loader.
未增加自定義配置的情況下,webpack只能處理JavaScript檔案和JSON檔案,Loader可以使webpack處理其他型別的檔案到模組中,新增到依賴圖中,被應用程式使用.
條件匹配: 通過 test / include / excluede 三個配置來匹配檔案,支援string和array
應用規則: 匹配命中檔案之後,使用use中的配置來應用loader,同時可以按"從後往前"的順序應用多個loader
重置順序: 一組loader的執行順序迷人是從右向左執行,通過 enforce 選項可以讓其中一個loader的執行順序放到最後或最前
const path = require('path'); module.exports = { output: { filename: 'my-first-webpack.bundle.js' }, module: { rules: [ // 在rules中可以設定多個規則 { test: /\.txt$/, // 設定用來匹配什麼檔案需要被轉換 // 只命中src目錄裡的js檔案,加快 Webpack 搜尋速度 include: path.resolve(__dirname, 'src'), // 排除 node_modules 目錄下的檔案 exclude: path.resolve(__dirname, 'node_modules'), // 處理順序為從後到前,即先交給 sass-loader 處理,再把結果交給 css-loader 最後再給 style-loader。 use: ['style-loader', 'css-loader', 'sass-loader'], // use中同樣可以設定Object use: [ { loader:'babel-loader', options:{ cacheDirectory:true, }, // enforce:'post' 的含義是把該 Loader 的執行順序放到最後 // enforce 的值還可以是 pre,代表把 Loader 的執行順序放到最前面 enforce:'post' }, ] } ] } }; 複製程式碼
外掛(plugins)
loaders用來轉換某些型別的模組(檔案),plugin 則用來執行,打包優化,資源管理和插入環境變數,等各種任務
plugin 是用來擴充套件 Webpack 功能的,通過在構建流程裡注入鉤子實現,它給 webpack 帶來了很大的靈活性。
通過require方法引入外掛,並將其例項化的物件加入plugins中
const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm const webpack = require('webpack'); //to access built-in plugins module.exports = { module: { rules: [ { test: /\.txt$/, use: 'raw-loader' } ] }, plugins: [ new HtmlWebpackPlugin({template: './src/index.html'}) ] }; 複製程式碼
上方例子, html-webpack-plugin 外掛會給應用生成一個HTML檔案,並在檔案中插入打包好的檔案.
tip: 使用 Plugin 的難點在於掌握 Plugin 本身提供的配置項,而不是如何在 Webpack 中接入 Plugin。
webpack打包的基本邏輯
- 初始化引數
從配置檔案和Shell語句中讀取併合並引數,得出最終的引數
- 開始編譯
用第一步得到的引數初始化Compiler物件,載入所有配置的外掛,執行物件中的run方法開始執行編譯
- 確認入口
根據配置中的entry找出所有的入口檔案
- 編譯模組
從入口檔案出發,呼叫所有配置的loader對模組進行翻譯,再找出該模組依賴的模組,遞迴查詢所有的模組依賴
- 完成模組編譯
經過第四部使用loader對所有模組進行翻譯後,得到每個模組被翻譯後的最終內容以及他們之間的依賴關係
- 輸出資源
根據入口和模組之間的依賴關係,組裝成一個個包含多個模組的Chunk,再把每個chunk轉換成一個單獨的檔案並加入到輸出列表中
- 輸出完成
確認好輸出內容後,根據配置確認輸出的路徑和檔名,把檔案內容寫入到檔案系統中
tip: 上述過程中,webpack會在特定時間廣播出特定時間,外掛接受到想要的事件廣播之後便會執行特性的邏輯,並且外掛可以呼叫webpack提供的API改變webpack的執行結果
輸出檔案分析
webpack配置檔案
const path = require('path') // Since webpack 4 the "extract-text-webpack-plugin" should not be used for css. // Use "mini-css-extract-plugin" instead // const ExtractTextPlugin = require('extract-text-webpack-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const merge = require('webpack-merge') // 這個在 npm run dev 和 npm run build 時候是不同的 const TARGET = process.env.npm_lifecycle_event const APP_PATH = path.join(__dirname, '/src') const dist = path.resolve(__dirname, 'dist') const common = { entry: `${APP_PATH}/index.js`, output: { path: dist, filename: 'index.js' } } let other = {} if (TARGET === 'dev') { other = { mode: 'development', module: { rules: [ { test: /\.css$/, // TODO 理解loader的執行順序 use: [ 'style-loader', // Adds CSS to the DOM by injecting a <style> tag 'css-loader' ] } ] }, plugins: [new HtmlWebpackPlugin({ template: `${APP_PATH}/index.html` })] } } if (TARGET === 'build') { console.log('就是這裡') other = { mode: 'development', module: { rules: [ { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, // 提取額外的css檔案 'css-loader' ] } ] }, plugins: [ new HtmlWebpackPlugin({ template: `${APP_PATH}/index.html` }), new MiniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[id].css' }) ] } } module.exports = merge(common, other) 複製程式碼
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Webpack</title> </head> <body></body> </html> 複製程式碼
index.js
require('./style.css') require('./moduleA.js') const b = require('./moduleB.js') setTimeout(() => { console.log('new b is running') b() }, 1000) 複製程式碼
moduleA.js
var newDiv = document.createElement('div') var newContent = document.createTextNode("Hi there! I'm module A!") newDiv.appendChild(newContent) document.body.appendChild(newDiv) 複製程式碼
moduleB.js
module.exports = function() { var newDiv = document.createElement('div') var newContent = document.createTextNode("Hi there! I'm module B!") newDiv.appendChild(newContent) document.body.appendChild(newDiv) } 複製程式碼
style.css
body { background: pink; } 複製程式碼
webpack.config.js
// webpack.config.js // entry的引數是String module.exports = { entry: './src/index.js'// 這是預設值,當然可以自定義 } // entry的引數是Array module.exports = { entry: ['./app/entry1', './app/entry2'] } // entry的引數是Object module.exports = { entry: { a: './app/entry-a', b: ['./app/entry-b1', './app/entry-b2'] } } // entry的引數可以同步函式 module.exports = { entry: () => { return { a:'./pages/a', b:'./pages/b', } } } // entry的引數可以非同步函式 module.exports = { entry: () => { return new Promise((resolve)=>{ resolve({ a:'./pages/a', b:'./pages/b', }); }); } } 複製程式碼
打包之後的檔案
;(function(modules) { // webpackBootstrap // The module cache var installedModules = {} // The require function function __webpack_require__(moduleId) { // Check if module is in cache // 判斷需要 require 的函式是否已經在快取中了 if (installedModules[moduleId]) { return installedModules[moduleId].exports } // Create a new module (and put it into the cache) // 初始化需要require的module,並儲存到快取中 var module = (installedModules[moduleId] = { i: moduleId, l: false, exports: {} }) // Execute the module function // 立即執行函式的引數 + moduleId 定位到當前需要執行的函式 // 這裡將執行webpack打包之後的函式 // 傳入module.exportsmodule 以及 require函式 // 然後將執行模組的內在邏輯,遞迴處理依賴, 並將模組的輸出儲存 debugger // debugger2 - 模組執行 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 } //----- 對主邏輯不重要的程式碼 - start ----- // 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 = '' //----- 對主邏輯不重要的程式碼 - ending ----- // Load entry module and return exports //debugger1 - 邏輯從這裡開始 // __webpack_require__ 即 require 函式 // 並設定了入口函式的key, moduleId = './src/index.js' return __webpack_require__((__webpack_require__.s = './src/index.js')) })({ './node_modules/css-loader/dist/cjs.js!./src/style.css': function( module, exports, __webpack_require__ ) { eval( 'exports = module.exports = __webpack_require__(/*! ../node_modules/css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js")(false);\n// Module\nexports.push([module.i, "body {\\nbackground: pink;\\n}", ""]);\n\n\n\n//# sourceURL=webpack:///./src/style.css?./node_modules/css-loader/dist/cjs.js' ) }, './node_modules/css-loader/dist/runtime/api.js': function( module, exports, __webpack_require__ ) { 'use strict' eval( "\n\n/*\nMIT License http://www.opensource.org/licenses/mit-license.php\nAuthor Tobias Koppers @sokra\n*/\n// css base code, injected by the css-loader\nmodule.exports = function (useSourceMap) {\nvar list = []; // return the list of modules as css string\n\nlist.toString = function toString() {\nreturn this.map(function (item) {\nvar content = cssWithMappingToString(item, useSourceMap);\n\nif (item[2]) {\nreturn '@media ' + item[2] + '{' + content + '}';\n} else {\nreturn content;\n}\n}).join('');\n}; // import a list of modules into the list\n\n\nlist.i = function (modules, mediaQuery) {\nif (typeof modules === 'string') {\nmodules = [[null, modules, '']];\n}\n\nvar alreadyImportedModules = {};\n\nfor (var i = 0; i < this.length; i++) {\nvar id = this[i][0];\n\nif (id != null) {\nalreadyImportedModules[id] = true;\n}\n}\n\nfor (i = 0; i < modules.length; i++) {\nvar item = modules[i]; // skip already imported module\n// this implementation is not 100% perfect for weird media query combinations\n// when a module is imported multiple times with different media queries.\n// I hope this will never occur (Hey this way we have smaller bundles)\n\nif (item[0] == null || !alreadyImportedModules[item[0]]) {\nif (mediaQuery && !item[2]) {\nitem[2] = mediaQuery;\n} else if (mediaQuery) {\nitem[2] = '(' + item[2] + ') and (' + mediaQuery + ')';\n}\n\nlist.push(item);\n}\n}\n};\n\nreturn list;\n};\n\nfunction cssWithMappingToString(item, useSourceMap) {\nvar content = item[1] || '';\nvar cssMapping = item[3];\n\nif (!cssMapping) {\nreturn content;\n}\n\nif (useSourceMap && typeof btoa === 'function') {\nvar sourceMapping = toComment(cssMapping);\nvar sourceURLs = cssMapping.sources.map(function (source) {\nreturn '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */';\n});\nreturn [content].concat(sourceURLs).concat([sourceMapping]).join('\\n');\n}\n\nreturn [content].join('\\n');\n} // Adapted from convert-source-map (MIT)\n\n\nfunction toComment(sourceMap) {\n// eslint-disable-next-line no-undef\nvar base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))));\nvar data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64;\nreturn '/*# ' + data + ' */';\n}\n\n//# sourceURL=webpack:///./node_modules/css-loader/dist/runtime/api.js?" ) }, './node_modules/style-loader/lib/addStyles.js': function( module, exports, __webpack_require__ ) { eval( '/*\n\tMIT License http://www.opensource.org/licenses/mit-license.php\n\tAuthor Tobias Koppers @sokra\n*/\n\nvar stylesInDom = {};\n\nvar\tmemoize = function (fn) {\n\tvar memo;\n\n\treturn function () {\n\t\tif (typeof memo === "undefined") memo = fn.apply(this, arguments);\n\t\treturn memo;\n\t};\n};\n\nvar isOldIE = memoize(function () {\n\t// Test for IE <= 9 as proposed by Browserhacks\n\t// @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805\n\t// Tests for existence of standard globals is to allow style-loader\n\t// to operate correctly into non-standard environments\n\t// @see https://github.com/webpack-contrib/style-loader/issues/177\n\treturn window && document && document.all && !window.atob;\n});\n\nvar getTarget = function (target, parent) {\nif (parent){\nreturn parent.querySelector(target);\n}\nreturn document.querySelector(target);\n};\n\nvar getElement = (function (fn) {\n\tvar memo = {};\n\n\treturn function(target, parent) {\n// If passing function in options, then use it for resolve "head" element.\n// Useful for Shadow Root style i.e\n// {\n//insertInto: function () { return document.querySelector("#foo").shadowRoot }\n// }\nif (typeof target === \'function\') {\nreturn target();\n}\nif (typeof memo[target] === "undefined") {\n\t\t\tvar styleTarget = getTarget.call(this, target, parent);\n\t\t\t// Special case to return head of iframe instead of iframe itself\n\t\t\tif (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {\n\t\t\t\ttry {\n\t\t\t\t\t// This will throw an exception if access to iframe is blocked\n\t\t\t\t\t// due to cross-origin restrictions\n\t\t\t\t\tstyleTarget = styleTarget.contentDocument.head;\n\t\t\t\t} catch(e) {\n\t\t\t\t\tstyleTarget = null;\n\t\t\t\t}\n\t\t\t}\n\t\t\tmemo[target] = styleTarget;\n\t\t}\n\t\treturn memo[target]\n\t};\n})();\n\nvar singleton = null;\nvar\tsingletonCounter = 0;\nvar\tstylesInsertedAtTop = [];\n\nvar\tfixUrls = __webpack_require__(/*! ./urls */ "./node_modules/style-loader/lib/urls.js");\n\nmodule.exports = function(list, options) {\n\tif (typeof DEBUG !== "undefined" && DEBUG) {\n\t\tif (typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment");\n\t}\n\n\toptions = options || {};\n\n\toptions.attrs = typeof options.attrs === "object" ? options.attrs : {};\n\n\t// Force single-tag solution on IE6-9, which has a hard limit on the # of <style>\n\t// tags it will allow on a page\n\tif (!options.singleton && typeof options.singleton !== "boolean") options.singleton = isOldIE();\n\n\t// By default, add <style> tags to the <head> element\nif (!options.insertInto) options.insertInto = "head";\n\n\t// By default, add <style> tags to the bottom of the target\n\tif (!options.insertAt) options.insertAt = "bottom";\n\n\tvar styles = listToStyles(list, options);\n\n\taddStylesToDom(styles, options);\n\n\treturn function update (newList) {\n\t\tvar mayRemove = [];\n\n\t\tfor (var i = 0; i < styles.length; i++) {\n\t\t\tvar item = styles[i];\n\t\t\tvar domStyle = stylesInDom[item.id];\n\n\t\t\tdomStyle.refs--;\n\t\t\tmayRemove.push(domStyle);\n\t\t}\n\n\t\tif(newList) {\n\t\t\tvar newStyles = listToStyles(newList, options);\n\t\t\taddStylesToDom(newStyles, options);\n\t\t}\n\n\t\tfor (var i = 0; i < mayRemove.length; i++) {\n\t\t\tvar domStyle = mayRemove[i];\n\n\t\t\tif(domStyle.refs === 0) {\n\t\t\t\tfor (var j = 0; j < domStyle.parts.length; j++) domStyle.parts[j]();\n\n\t\t\t\tdelete stylesInDom[domStyle.id];\n\t\t\t}\n\t\t}\n\t};\n};\n\nfunction addStylesToDom (styles, options) {\n\tfor (var i = 0; i < styles.length; i++) {\n\t\tvar item = styles[i];\n\t\tvar domStyle = stylesInDom[item.id];\n\n\t\tif(domStyle) {\n\t\t\tdomStyle.refs++;\n\n\t\t\tfor(var j = 0; j < domStyle.parts.length; j++) {\n\t\t\t\tdomStyle.parts[j](item.parts[j]);\n\t\t\t}\n\n\t\t\tfor(; j < item.parts.length; j++) {\n\t\t\t\tdomStyle.parts.push(addStyle(item.parts[j], options));\n\t\t\t}\n\t\t} else {\n\t\t\tvar parts = [];\n\n\t\t\tfor(var j = 0; j < item.parts.length; j++) {\n\t\t\t\tparts.push(addStyle(item.parts[j], options));\n\t\t\t}\n\n\t\t\tstylesInDom[item.id] = {id: item.id, refs: 1, parts: parts};\n\t\t}\n\t}\n}\n\nfunction listToStyles (list, options) {\n\tvar styles = [];\n\tvar newStyles = {};\n\n\tfor (var i = 0; i < list.length; i++) {\n\t\tvar item = list[i];\n\t\tvar id = options.base ? item[0] + options.base : item[0];\n\t\tvar css = item[1];\n\t\tvar media = item[2];\n\t\tvar sourceMap = item[3];\n\t\tvar part = {css: css, media: media, sourceMap: sourceMap};\n\n\t\tif(!newStyles[id]) styles.push(newStyles[id] = {id: id, parts: [part]});\n\t\telse newStyles[id].parts.push(part);\n\t}\n\n\treturn styles;\n}\n\nfunction insertStyleElement (options, style) {\n\tvar target = getElement(options.insertInto)\n\n\tif (!target) {\n\t\tthrow new Error("Couldn\'t find a style target. This probably means that the value for the \'insertInto\' parameter is invalid.");\n\t}\n\n\tvar lastStyleElementInsertedAtTop = stylesInsertedAtTop[stylesInsertedAtTop.length - 1];\n\n\tif (options.insertAt === "top") {\n\t\tif (!lastStyleElementInsertedAtTop) {\n\t\t\ttarget.insertBefore(style, target.firstChild);\n\t\t} else if (lastStyleElementInsertedAtTop.nextSibling) {\n\t\t\ttarget.insertBefore(style, lastStyleElementInsertedAtTop.nextSibling);\n\t\t} else {\n\t\t\ttarget.appendChild(style);\n\t\t}\n\t\tstylesInsertedAtTop.push(style);\n\t} else if (options.insertAt === "bottom") {\n\t\ttarget.appendChild(style);\n\t} else if (typeof options.insertAt === "object" && options.insertAt.before) {\n\t\tvar nextSibling = getElement(options.insertAt.before, target);\n\t\ttarget.insertBefore(style, nextSibling);\n\t} else {\n\t\tthrow new Error("[Style Loader]\\n\\n Invalid value for parameter \'insertAt\' (\'options.insertAt\') found.\\n Must be \'top\', \'bottom\', or Object.\\n (https://github.com/webpack-contrib/style-loader#insertat)\\n");\n\t}\n}\n\nfunction removeStyleElement (style) {\n\tif (style.parentNode === null) return false;\n\tstyle.parentNode.removeChild(style);\n\n\tvar idx = stylesInsertedAtTop.indexOf(style);\n\tif(idx >= 0) {\n\t\tstylesInsertedAtTop.splice(idx, 1);\n\t}\n}\n\nfunction createStyleElement (options) {\n\tvar style = document.createElement("style");\n\n\tif(options.attrs.type === undefined) {\n\t\toptions.attrs.type = "text/css";\n\t}\n\n\tif(options.attrs.nonce === undefined) {\n\t\tvar nonce = getNonce();\n\t\tif (nonce) {\n\t\t\toptions.attrs.nonce = nonce;\n\t\t}\n\t}\n\n\taddAttrs(style, options.attrs);\n\tinsertStyleElement(options, style);\n\n\treturn style;\n}\n\nfunction createLinkElement (options) {\n\tvar link = document.createElement("link");\n\n\tif(options.attrs.type === undefined) {\n\t\toptions.attrs.type = "text/css";\n\t}\n\toptions.attrs.rel = "stylesheet";\n\n\taddAttrs(link, options.attrs);\n\tinsertStyleElement(options, link);\n\n\treturn link;\n}\n\nfunction addAttrs (el, attrs) {\n\tObject.keys(attrs).forEach(function (key) {\n\t\tel.setAttribute(key, attrs[key]);\n\t});\n}\n\nfunction getNonce() {\n\tif (false) {}\n\n\treturn __webpack_require__.nc;\n}\n\nfunction addStyle (obj, options) {\n\tvar style, update, remove, result;\n\n\t// If a transform function was defined, run it on the css\n\tif (options.transform && obj.css) {\n\tresult = typeof options.transform === \'function\'\n\t\t ? options.transform(obj.css) \n\t\t : options.transform.default(obj.css);\n\n\tif (result) {\n\t\t// If transform returns a value, use that instead of the original css.\n\t\t// This allows running runtime transformations on the css.\n\t\tobj.css = result;\n\t} else {\n\t\t// If the transform function returns a falsy value, don\'t add this css.\n\t\t// This allows conditional loading of css\n\t\treturn function() {\n\t\t\t// noop\n\t\t};\n\t}\n\t}\n\n\tif (options.singleton) {\n\t\tvar styleIndex = singletonCounter++;\n\n\t\tstyle = singleton || (singleton = createStyleElement(options));\n\n\t\tupdate = applyToSingletonTag.bind(null, style, styleIndex, false);\n\t\tremove = applyToSingletonTag.bind(null, style, styleIndex, true);\n\n\t} else if (\n\t\tobj.sourceMap &&\n\t\ttypeof URL === "function" &&\n\t\ttypeof URL.createObjectURL === "function" &&\n\t\ttypeof URL.revokeObjectURL === "function" &&\n\t\ttypeof Blob === "function" &&\n\t\ttypeof btoa === "function"\n\t) {\n\t\tstyle = createLinkElement(options);\n\t\tupdate = updateLink.bind(null, style, options);\n\t\tremove = function () {\n\t\t\tremoveStyleElement(style);\n\n\t\t\tif(style.href) URL.revokeObjectURL(style.href);\n\t\t};\n\t} else {\n\t\tstyle = createStyleElement(options);\n\t\tupdate = applyToTag.bind(null, style);\n\t\tremove = function () {\n\t\t\tremoveStyleElement(style);\n\t\t};\n\t}\n\n\tupdate(obj);\n\n\treturn function updateStyle (newObj) {\n\t\tif (newObj) {\n\t\t\tif (\n\t\t\t\tnewObj.css === obj.css &&\n\t\t\t\tnewObj.media === obj.media &&\n\t\t\t\tnewObj.sourceMap === obj.sourceMap\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tupdate(obj = newObj);\n\t\t} else {\n\t\t\tremove();\n\t\t}\n\t};\n}\n\nvar replaceText = (function () {\n\tvar textStore = [];\n\n\treturn function (index, replacement) {\n\t\ttextStore[index] = replacement;\n\n\t\treturn textStore.filter(Boolean).join(\'\\n\');\n\t};\n})();\n\nfunction applyToSingletonTag (style, index, remove, obj) {\n\tvar css = remove ? "" : obj.css;\n\n\tif (style.styleSheet) {\n\t\tstyle.styleSheet.cssText = replaceText(index, css);\n\t} else {\n\t\tvar cssNode = document.createTextNode(css);\n\t\tvar childNodes = style.childNodes;\n\n\t\tif (childNodes[index]) style.removeChild(childNodes[index]);\n\n\t\tif (childNodes.length) {\n\t\t\tstyle.insertBefore(cssNode, childNodes[index]);\n\t\t} else {\n\t\t\tstyle.appendChild(cssNode);\n\t\t}\n\t}\n}\n\nfunction applyToTag (style, obj) {\n\tvar css = obj.css;\n\tvar media = obj.media;\n\n\tif(media) {\n\t\tstyle.setAttribute("media", media)\n\t}\n\n\tif(style.styleSheet) {\n\t\tstyle.styleSheet.cssText = css;\n\t} else {\n\t\twhile(style.firstChild) {\n\t\t\tstyle.removeChild(style.firstChild);\n\t\t}\n\n\t\tstyle.appendChild(document.createTextNode(css));\n\t}\n}\n\nfunction updateLink (link, options, obj) {\n\tvar css = obj.css;\n\tvar sourceMap = obj.sourceMap;\n\n\t/*\n\t\tIf convertToAbsoluteUrls isn\'t defined, but sourcemaps are enabled\n\t\tand there is no publicPath defined then lets turn convertToAbsoluteUrls\n\t\ton by default.Otherwise default to the convertToAbsoluteUrls option\n\t\tdirectly\n\t*/\n\tvar autoFixUrls = options.convertToAbsoluteUrls === undefined && sourceMap;\n\n\tif (options.convertToAbsoluteUrls || autoFixUrls) {\n\t\tcss = fixUrls(css);\n\t}\n\n\tif (sourceMap) {\n\t\t// http://stackoverflow.com/a/26603875\n\t\tcss += "\\n/*# sourceMappingURL=data:application/json;base64," + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + " */";\n\t}\n\n\tvar blob = new Blob([css], { type: "text/css" });\n\n\tvar oldSrc = link.href;\n\n\tlink.href = URL.createObjectURL(blob);\n\n\tif(oldSrc) URL.revokeObjectURL(oldSrc);\n}\n\n\n//# sourceURL=webpack:///./node_modules/style-loader/lib/addStyles.js?' ) }, './node_modules/style-loader/lib/urls.js': function(module, exports) { eval( '\n/**\n * When source maps are enabled, `style-loader` uses a link element with a data-uri to\n * embed the css on the page. This breaks all relative urls because now they are relative to a\n * bundle instead of the current page.\n *\n * One solution is to only use full urls, but that may be impossible.\n *\n * Instead, this function "fixes" the relative urls to be absolute according to the current page location.\n *\n * A rudimentary test suite is located at `test/fixUrls.js` and can be run via the `npm test` command.\n *\n */\n\nmodule.exports = function (css) {\n// get current location\nvar location = typeof window !== "undefined" && window.location;\n\nif (!location) {\nthrow new Error("fixUrls requires window.location");\n}\n\n\t// blank or null?\n\tif (!css || typeof css !== "string") {\n\treturn css;\n}\n\nvar baseUrl = location.protocol + "//" + location.host;\nvar currentDir = baseUrl + location.pathname.replace(/\\/[^\\/]*$/, "/");\n\n\t// convert each url(...)\n\t/*\n\tThis regular expression is just a way to recursively match brackets within\n\ta string.\n\n\t /url\\s*\\(= Match on the word "url" with any whitespace after it and then a parens\n\t(= Start a capturing group\n\t(?:= Start a non-capturing group\n\t[^)(]= Match anything that isn\'t a parentheses\n\t|= OR\n\t\\(= Match a start parentheses\n\t(?:= Start another non-capturing groups\n\t[^)(]+= Match anything that isn\'t a parentheses\n\t|= OR\n\t\\(= Match a start parentheses\n\t[^)(]*= Match anything that isn\'t a parentheses\n\t\\)= Match a end parentheses\n\t)= End Group\n*\\) = Match anything and then a close parens\n)= Close non-capturing group\n*= Match anything\n)= Close capturing group\n\t \\)= Match a close parens\n\n\t /gi= Get all matches, not the first.Be case insensitive.\n\t */\n\tvar fixedCss = css.replace(/url\\s*\\(((?:[^)(]|\\((?:[^)(]+|\\([^)(]*\\))*\\))*)\\)/gi, function(fullMatch, origUrl) {\n\t\t// strip quotes (if they exist)\n\t\tvar unquotedOrigUrl = origUrl\n\t\t\t.trim()\n\t\t\t.replace(/^"(.*)"$/, function(o, $1){ return $1; })\n\t\t\t.replace(/^\'(.*)\'$/, function(o, $1){ return $1; });\n\n\t\t// already a full url? no change\n\t\tif (/^(#|data:|http:\\/\\/|https:\\/\\/|file:\\/\\/\\/|\\s*$)/i.test(unquotedOrigUrl)) {\n\t\treturn fullMatch;\n\t\t}\n\n\t\t// convert the url to a full url\n\t\tvar newUrl;\n\n\t\tif (unquotedOrigUrl.indexOf("//") === 0) {\n\t\t\t//TODO: should we add protocol?\n\t\t\tnewUrl = unquotedOrigUrl;\n\t\t} else if (unquotedOrigUrl.indexOf("/") === 0) {\n\t\t\t// path should be relative to the base url\n\t\t\tnewUrl = baseUrl + unquotedOrigUrl; // already starts with \'/\'\n\t\t} else {\n\t\t\t// path should be relative to current directory\n\t\t\tnewUrl = currentDir + unquotedOrigUrl.replace(/^\\.\\//, ""); // Strip leading \'./\'\n\t\t}\n\n\t\t// send back the fixed url(...)\n\t\treturn "url(" + JSON.stringify(newUrl) + ")";\n\t});\n\n\t// send back the fixed css\n\treturn fixedCss;\n};\n\n\n//# sourceURL=webpack:///./node_modules/style-loader/lib/urls.js?' ) }, './src/index.js': function(module, exports, __webpack_require__) { eval( '__webpack_require__(/*! ./style.css */ "./src/style.css")\n__webpack_require__(/*! ./moduleA.js */ "./src/moduleA.js")\nconst b = __webpack_require__(/*! ./moduleB.js */ "./src/moduleB.js")\nconsole.log(1)\nsetTimeout(() => {\nconsole.log(\'new b is running\')\nb()\n}, 1000)\n\n\n//# sourceURL=webpack:///./src/index.js?' ) }, './src/moduleA.js': function(module, exports) { eval( "var newDiv = document.createElement('div') \nvar newContent = document.createTextNode(\"Hi there! I'm module A!\")\nnewDiv.appendChild(newContent)\ndocument.body.appendChild(newDiv)\n\n//# sourceURL=webpack:///./src/moduleA.js?" ) }, './src/moduleB.js': function(module, exports) { eval( "module.exports = function() {\nvar newDiv = document.createElement('div')\nvar newContent = document.createTextNode(\"Hi there! I'm module B!\")\nnewDiv.appendChild(newContent)\ndocument.body.appendChild(newDiv)\n}\n\n\n//# sourceURL=webpack:///./src/moduleB.js?" ) }, './src/style.css': function(module, exports, __webpack_require__) { // 這裡可以看到 css-loader 對css檔案出的處理 // css的內容被儲存到了'./node_modules/css-loader/dist/cjs.js!./src/style.css' 中 // './node_modules/style-loader/lib/addStyles.js'對應的函式則是將css插入html的邏輯 eval( '\nvar content = __webpack_require__(/*! !../node_modules/css-loader/dist/cjs.js!./style.css */ "./node_modules/css-loader/dist/cjs.js!./src/style.css");\n\nif(typeof content === \'string\') content = [[module.i, content, \'\']];\n\nvar transform;\nvar insertInto;\n\n\n\nvar options = {"hmr":true}\n\noptions.transform = transform\noptions.insertInto = undefined;\n\nvar update = __webpack_require__(/*! ../node_modules/style-loader/lib/addStyles.js */ "./node_modules/style-loader/lib/addStyles.js")(content, options);\n\nif(content.locals) module.exports = content.locals;\n\nif(false) {}\n\n//# sourceURL=webpack:///./src/style.css?' ) } }) 複製程式碼
編寫簡單的plugins
一個最基礎的 Plugin 的程式碼是這樣的:
class BasicPlugin{ // 在建構函式中獲取使用者給該外掛傳入的配置 constructor(options){ } // Webpack 會呼叫 BasicPlugin 例項的 apply 方法給外掛例項傳入 compiler 物件 apply(compiler){ compiler.plugin('compilation',function(compilation) { }) } } // 匯出 Plugin module.exports = BasicPlugin; 複製程式碼
在使用這個 Plugin 時,相關配置程式碼如下:
const BasicPlugin = require('./BasicPlugin.js'); module.export = { plugins:[ new BasicPlugin(options), ] } 複製程式碼