webpack是什麼?
https://webpack.js.org/concepts/
https://code.tutsplus.com/tutorials/introduction-to-webpack-part-1--cms-25791
webpack是一個為現代javascript application而生的module bundler:模組打包器。
某種意義上說,webpack也是可以代替gulp,grunt等傳統意義上的task runner,而webpack中的loader就來實現gulp,grunt工具鏈build時的不同功能,比如concat,uglify等。
它支援非常多的配置,擁有強大的功能。但也正是因為這一點可能導致我們很多人一團霧水。在學習之前,先搞明白幾個概念:
https://webpack.js.org/glossary/
Module, Chunk, Bundle
{ entry: { foo: ["webpack/hot/only-dev-server.js","./src/foo.js"], bar: ["./src/bar.js"] }, output: { path: "./dist", filename: "[name].js" } }
- Modules: "webpack/hot/only-dev-server.js", "./src/foo.js", "./src/bar.js" ( + any other modules that are dependencies of these entry points!)
- Chunks: foo, bar
- Bundles: foo, bar
上面的樣例中chunks和bundles是1:1 關係, 但是這並不是總對的. 比如,如果你增加了sourcemaps,你將會獲得1:2 的關係 chunk : bundle.
Entry
webpack經過編譯後將會為你的application建立一張依賴圖:dependency graph. 而這張依賴圖的起點就被稱之為: entry point. 這個entry point告訴webpakc從哪裡開始根據掃描require(cmd), import(es6)等呼叫來不斷描繪這張依賴圖從而知道要打包哪些元素到最終的bundle中。
你可以將application的entry point看作 contextual root或者the first file to kick off your app
在webpack configuration object中,我們就使用entry這個屬性來配置,最簡單的例子:
module.exports = { entry: './path/to/my/entry/file.js' };
entry point的設定有多種形式:
單個字串:
const config = { entry: './path/to/my/entry/file.js' }; module.exports = config;
陣列字串:
const config = { entry: ['./path/to/lib/library.js','./path/to/my/entry/main.js'] }; module.exports = config;
object syntax:
const config = { entry: { main: './path/to/my/entry/file.js' } };
const config = { entry: { app: './src/app.js', vendors: './src/vendors.js' } };
const config = { entry: { pageOne: './src/pageOne/index.js', pageTwo: './src/pageTwo/index.js', pageThree: './src/pageThree/index.js' } };
具體參考: https://webpack.js.org/concepts/entry-points/
Output
一旦你已經將你的assets全部打包在一起了,我們仍然需要告訴webpack應該將我們的application bundle放到哪裡.
這是通過output 屬性來定義:(output也會有很多其他的屬性,比如filename,path...)
var path = require('path'); module.exports = { entry: './path/to/my/entry/file.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'my-first-webpack.bundle.js' } };
Loaders
Loader是webpack中非常重要的概念。webpack的目標是將專案中的所有assets都可以由webpack來打包,而不是直接放到瀏覽器那裡(通過script tag, link tag css)去非同步載入。(注意這也並不意味著這些assets都必須打包在一起,可以有些折中). webpack將任何一個檔案(.css,.html,.less,.scss,.jpg...)都視作一個javascript module.然而,webpack僅能夠理解javascript!
而Loaders就能夠將這些webpack本身並不能夠理解和認識的檔案轉化為一個module從而加入到bundle的依賴樹中去。
loader相關的配置從高層上講有兩個目的:
1. 指定什麼檔案型別應該由一個Loader來transform (通過test 屬性來完成)
2. 指定應該由哪個loader來transform作為bundle的輸入
var 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: [ {test: /\.(js|jsx)$/, use: 'babel-loader'} ] } }; module.exports = config;
上面的配置就像是我們要告訴webpack的編譯器:
"
Hey webpack compiler,when you come across a path that resolves to a '.js' or '.jsx' file inside of a require
/import
statement, use the babel-loader
to transform it before you add it to the bundle
"
建立一個自己的loader
大部分時間,你所需要的webpack功能,都能通過已經存在的loader來幫你完成,但是事情總歸沒有那麼簡單,有時候你需要特殊的檔案處理流程,比如,如果我們希望在require一個json檔案之前我們先把這個json檔案中的comments全部清除,這時,我們已知有一個node module名叫:strip-json-comments,他可以實現這個功能,我們當然可以直接使用這個module來作為loader,配置json檔案被require之前來使用,但是我們又希望在此基礎上列印出strip comments之前和之後的內容,這時,我們就可以以此為基礎來創作一個自己的loader~!
// 在strip.js檔案中定義loader的邏輯 var stripComments = require('strip-json-comments') module.exports = function (source) { this.cacheable() console.log('source:',source) console.log('sourceStripped:',stripComments(source)) return stripComments(source) } // 在webpack.config.js loaders section定義如下: { test: /\.json$/, exclude: /node_modules/, use: ["json-loader",path.resolve('src/customloaders/strip')] } // 在app.js中 var config = require('../appconfig/jsonconfig.json') // 這時就將呼叫到我們的strip loader列印出strip之前和之後的結果,最終呼叫strip-loader來返回結果 console.log(config);
Plugin
Loader本身只能對一個個檔案來做transform,而plugin則通常用於對bundled module的chunks或者compilations執行一些特定的action並且定製部分功能。要使用一個plugin,我們需要require(),並且放在plugins屬性中。webpack本身已經提供了很多plugin,我們也可以自己建立適合我們特定需求的plugin. plugin更像grunt或者gulp的tasks.
plugin本身將webpack engine的所有能力暴露給第三方開發者。通過使用階段性的構建callback,開發者可以向webpack的build process中注入自己的期望行為。
const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm const webpack = require('webpack'); //to access built-in plugins const config = { entry: './path/to/my/entry/file.js', output: { filename: 'my-first-webpack.bundle.js', path: './dist' }, module: { rules: [ {test: /\.(js|jsx)$/, use: 'babel-loader'} ] }, plugins: [ new webpack.optimize.UglifyJsPlugin(), new HtmlWebpackPlugin({template: './src/index.html'}) ] }; module.exports = config;
建立一個自己的webpack plugin
https://webpack.js.org/contribute/writing-a-plugin/
一個webpack的plugin由以下幾個步驟:
1. 建立一個命名js函式
2.在該命名函式中定一個apply方法
3.指定將該函式繫結到webpack內部的event hook
4.操作webpack internal instance的特定資料
5.在函式完成時呼叫webpack提供的callback函式繼續webpack構建流程
// A named JavaScript function. function MyExampleWebpackPlugin() { }; // Defines `apply` method in it's prototype. MyExampleWebpackPlugin.prototype.apply = function(compiler) { // Specifies webpack's event hook to attach itself. compiler.plugin('webpacksEventHook', function(compilation /* Manipulates webpack internal instance specific data. */, callback) { console.log("This is an example plugin!!!"); // some other code logic to custom webpack build // Invokes webpack provided callback after functionality is complete. callback(); }); };
compiler和compilation物件
在plugin開發時最重要的就是要使用好compiler和compilation兩個objects.下面我們來仔細梳理一下他們在擴充套件webpack engine開發時所擔當的角色.
compiler object代表了配置好的完整webpack environment.這個物件一旦啟動了webpack就被建立,並且該物件會被包括options,loaders,plugins等配置項所配置。當應用一個plugin到webpack環境時,plugin將會接收一個對這個compiler物件的引用。我們使用compiler物件來訪問main webpack environment.
compilation物件則代表了一個versioned assets的一個single build.當執行webpack development middleware時,一旦監測到檔案變更,那麼就會建立一個新的compilation,這樣就新生成了一套編譯後的assets.每一個compilation物件使得module resources,compiled assets,changed files以及watched dependencies有一個參照水平面.該物件也提供了很多個callback points供plugin選擇使用以便定製行為。
這兩個元件是任何plugin必將關心的部分(特別是一個compilation物件),因此建議好好閱讀以下原始碼。
plugin的基礎架構:
plugins本質上是一些在其原型上定義了apply方法的例項化objects.這個apply方法由webpack compiler在安裝對應plugin時來呼叫一次。apply方法將被傳入對webpack compiler的引用,而通過這個compiler引用就授權了對compiler callbacks的訪問。
定義和使用hello plugin:
function HelloWorldPlugin(options) { // Setup the plugin instance with options... } HelloWorldPlugin.prototype.apply = function(compiler) { compiler.plugin('done', function() { console.log('Hello World!'); }); }; module.exports = HelloWorldPlugin; // 使用時: var HelloWorldPlugin = require('hello-world'); var webpackConfig = { // ... config settings here ... plugins: [ new HelloWorldPlugin({options: true}) ] }; Accessing th
訪問compilation物件:
使用compiler object,你可以繫結到callbacks,並且能夠訪問到每個new compilation.這些compilations又提供了能夠hooking到build process不同步驟的callbacks.
比如:
function HelloCompilationPlugin(options) {} HelloCompilationPlugin.prototype.apply = function(compiler) { // Setup callback for accessing a compilation: compiler.plugin("compilation", function(compilation) { // Now setup callbacks for accessing compilation steps: compilation.plugin("optimize", function() { console.log("Assets are being optimized."); }); }); }; module.exports = HelloCompilationPlugin;
async compilation plugins:
一些compilation plugin steps是非同步的,將傳入一個callback function進來,當你的plugin完成工作後必須呼叫這個callback以便build系統正常完成.
function HelloAsyncPlugin(options) {} HelloAsyncPlugin.prototype.apply = function(compiler) { compiler.plugin("emit", function(compilation, callback) { // Do something async... setTimeout(function() { console.log("Done with async work..."); callback(); }, 1000); }); }; module.exports = HelloAsyncPlugin;
一個完整的複雜plugin例子:
該plugin實現build完成時,羅列出所有assets檔案集合,生成一個新的asset檔案
定義:
function FileListPlugin(options) {} FileListPlugin.prototype.apply = function(compiler) { compiler.plugin('emit', function(compilation, callback) { // Create a header string for the generated file: var filelist = 'In this build:\n\n'; // Loop through all compiled assets, // adding a new line item for each filename. for (var filename in compilation.assets) { filelist += ('- '+ filename +'\n'); } // Insert this list into the webpack build as a new file asset: compilation.assets['filelist.md'] = { source: function() { return filelist; }, size: function() { return filelist.length; } }; callback(); }); }; module.exports = FileListPlugin;
使用:
var FileListPlugin = require('file-list'); var webpackConfig = { // ... config settings here ... plugins: [ new FileListPlugin() ] };
webpack compiler event hook and types
entry-option
after-plugins
compiler
after-resolvers
compiler
environment
after-environment
before-run
compiler.run()
startscompiler
run
compiler
watch-run
compiler
normal-module-factory
NormalModuleFactory
normalModuleFactory
context-module-factory
ContextModuleFactory
contextModuleFactory
before-compile
compilationParams
compile
compilationParams
this-compilation
compilation
eventcompilation
compilation
compilation
make
compilation
after-compile
compilation
should-emit
compilation
need-additional-pass
emit
compilation
after-emit
compilation
done
stats
failed
error
invalid
fileName
, changeTime
watch-close
https://webpack.js.org/contribute/writing-a-plugin/#different-plugin-shapes
Different Plugin Shapes:
根據plugin註冊到的事件型別我們可以把外掛劃分為不同的型別。每個事件鉤子來定義它是如何在其註冊中來應用這些外掛的。
synchrouous:
applyPlugins/applyPluginsBailResult
waterfull:
applyPluginsWaterfall
asynchronous :
applyPluginsAsync
async waterfall:
applyPluginsAsyncWaterfall
async series:
applyPluginsAsyncSeries
parallel:
applyPluginsParallel,applyPluginsParallelBailResult
https://webpack.js.org/contribute/writing-a-plugin/#different-plugin-shapes
webpack的依賴分析
方法: profile: true配置,使用一個plugin來生成stats.json檔案https://github.com/unindented/stats-webpack-plugin,隨後在http://webpack.github.io/analyse頁面選擇這個stats.json檔案就能分析出來下面的圖形來,好強大,好夢幻!
如何使用webpack-stats-graph來清晰分析bundle構成並做優化?
choco install graphviz // 注意choco是windows下的package manager方便安裝一些package
npm install -g webpack-stats-graph
webpack-stats-graph # by default looks for stats.json in current directory
webpack-stats-graph --help
https://github.com/g0t4/webpack-stats-graph
webpack-dev-server
webpack-dev-server是一個專門用於webpack開發的web服務,一旦啟動該服務,它也會watch檔案的變化,當檔案變化時,自動編譯,但是注意:它並不會將編譯的輸出寫到檔案系統中,而是直接serve,這個webpack --watch是有區別的,因為這種模式下是會直接寫檔案到磁碟上的。
下面一張圖解釋webpack-dev-server的各個引數及其作用.其中最重要的是publicPath和contentBase兩個引數。publicPath指定dev server編譯後的webpack bundle訪問的url地址,耳contentBase則指向非webpack bundle的靜態內容所在的起始目錄(比如index.html)。很多時候,如果你是在整合環境中開發,比如使用laravel作為後端,那麼這時所謂index.html實際上應該是由laravel blade編譯後提供的,這時這個contentBase引數就無所謂了,我們就只需要將bunlder放到blade模板中做好正確的引用即可。
如何除錯webpack-dev-server的某些問題?
很多時候如果webpack-dev-server工作不如你的意,那麼你可以通過: http://localhost:8080/webpack-dev-server 來檢視。很多時候都是因為地址不對的緣故.
如何讓webpack-dev-server可以在外網訪問?
很多時候使用linux作為開發和部署伺服器都是比較合適的,但是我們自己家裡又沒有linux環境,往往會使用一個雲主機來做開發用機。這時可能就需要webpack-dev-server能夠被外網訪問了.很簡單加上“webpack-dev-server --hot --host 0.0.0.0 --port 9999”
簡單webpack bundle的內容學習
app.js require login.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); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { __webpack_require__(1) // 唯一一個entry入口: app.js,載入login.js(require) document.write("app.js loaded and new info displayed") console.log('app loaded') /***/ }), /* 1 */ /***/ (function(module, exports) { console.log('login loaded') /***/ }) /******/ ]);
app.js require login.js and secondmodule.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); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { __webpack_require__(1); // 載入第一個entry app.js module.exports = __webpack_require__(4); // 載入第二個entry utils.js /***/ }), /* 1 */ /***/ (function(module, exports, __webpack_require__) { __webpack_require__(2) __webpack_require__(3) document.write("app.js loaded and new info displayed") console.log('app loaded') /***/ }), /* 2 */ /***/ (function(module, exports) { console.log('login loaded') /***/ }), /* 3 */ /***/ (function(module, exports) { console.log('this is the second module loaded by app') /***/ }), /* 4 */ /***/ (function(module, exports) { // utils.js放在entry陣列中作為multi-entry build // global lib for example jquery can be put into entry console.log('utils.js loaded') /***/ }) /******/ ]);
如何在js中引入css(注意webpack2.5.1以上的區別)
在webpack.config.js的module section中,增加rules欄位,指定css必須使用style-loader和css-loader,而這一點在webpack2.5.1之前只需要在loaders section增加對應配置!
module:{ // comment out jshint rules: [ // { // test: /\.js$/, // exclude: /node_modules/, // enforce: "pre", // loader: 'jshint-loader' // }, { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] } ] ... }
webpack code splitting and ajax loading bundle
http://jilles.me/webpack-async-bundle-loading/
https://toddmotto.com/lazy-loading-angular-code-splitting-webpack
loader vs plugin
loader完成對幾乎任何檔案格式的的預處理轉換,比如通過以下程式碼就可以呼叫你自己的loader來實現對相關require檔案的轉換,只有轉換之後才能進入bundle.
require("my-loader!./my-awesome-module")
和plugin相比,loader要簡單很多,因為他們僅僅暴露給webpack一個簡單的函式,並且不具有影響到webpack的實際build過程的能力。
而, plugin則不同,plugin可以深度整合到webpack中,因為他們可以通過註冊webpack build system的一些鉤子今兒可以訪問或者更改compiler, compilation,從而更改webpack的構建過程。
loader只為一個單一的檔案在進入bundle之前做變換。plugin則往往處理的物件是bundle或者chunk level,並且往往在bundle genneration的結尾時開始被呼叫工作。
使用Nodejs V8 Inspector Manager結合chrome除錯webpack執行過程
如果我們安裝了chrome的v8 inspector manager的話,可以結合chrome dev tool來除錯webpack的執行過程,對學習和開發webpack構建是一個很大的幫助。
https://mattdesl.svbtle.com/debugging-nodejs-in-chrome-devtools
node --inspect-brk build/build.js
Debugger listening on ws://127.0.0.1:9229/44fed39e-501a-43d3-b928-8e0a51eebc03
For help see https://nodejs.org/en/docs/inspector
之後使用chrome連線,下面就是使用chrome dev tool開啟的vue-cli建立的webpack專案對應做product build時的配置物件。
node --inspect-brk ./node_modules/jest/bin/jest --runInBand --no-cache --no-watchman
除錯npm run dev的方法
node --inspect-brk ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --progress --config "build/webpack.dev.conf.js"
vue-cli npm run dev時可能報code generator exceeds the max of "500KB"錯誤
10% building modules 4/4 modules 0 active[BABEL] Note: The code generator has deoptimised the styling of "D:/devenv/Code/newkidsit/resources/assets/js/vueapp/node_modules/lodash/lodash.js" as it exceeds the max of "500KB". 95% emitting ERROR Failed to compile with 1 errors11:08:48
解決辦法,bable增加compact: false:
{ test: /\.js$/, loader: 'babel-loader', query: {compact: false} }
如何解決context載入時不必要的載入?以momentjs的locale為例
https://webpack.js.org/plugins/context-replacement-plugin/
new webpack.ContextReplacementPlugin( /moment[\/\\]locale$/, /zh-cn/ )
https://stackoverflow.com/questions/25384360/how-to-prevent-moment-js-from-loading-locales-with-webpack
https://github.com/moment/moment/issues/2373
resolve.mainFields package.json中的main,module, browser是什麼意思?
https://webpack.js.org/configuration/resolve/#resolve-mainfields
https://github.com/defunctzombie/package-browser-field-spec
當我們import一個npm package時,比如
import * as D3 from "d3"
時,這個resolve.mainFeilds的配置選項將決定package.json中的哪個欄位的值被檢查並使用。預設值將隨著webpack configuration檔案的target選項而不同,而target配置專案具有"node","webworker","web"(預設值),"electron-main"。其中"node" target用於nodejs的服務端環境;"web" target用於一個browser-like的環境,這是預設配置。
如果target欄位設定為“web”,"webworker"或者不做設定的話,mainFields的預設值順序如下:
mainFields: ["browser", "module", "main"]
對於任何其他的非web,webworker值設定,則mainFields的預設值順序如下:
mainFields: ["module", "main"]
比如,D3js的package.json包含以下欄位:
{ ... main: 'build/d3.Node.js', browser: 'build/d3.js', module: 'index', ... }
這意味著,在web target的情況下,import * as D3 from "d3"將會使用browser欄位中指定的檔案:build/d3.js,因為browser欄位在web target下為第一個出現的值。而如果我們為node target做d3的build則引入的是module欄位的檔案:
如何載入一個模組下面的某個檔案的部分內容?比如main檔案中並未export,而其他的utils.js部分函式希望被引用??
// 注意qiniu-js目錄下的index.js是其“主"檔案,export了一些函式, // 但是我們希望使用base64目錄下面的urlSafeBase64Encode函式,就可以 // 像下面的語法來挑選我們想用的函式 import { urlSafeBase64Encode } from 'qiniu-js/src/base64'
tree-shaking and side-effect
很多時候,我們import一個module時,可能僅僅用到其中的一兩個export函式,大部分程式碼為無用的死程式碼,如何能夠找到這些死程式碼,並且在webpack4做bundle時剔除這些無用程式碼呢?
https://webpack.js.org/guides/tree-shaking/
harmony modules = webpack builtin ES2015 module
何時使用module.noParse配置項?
module.noParse配置項用於阻止webpack掃描解析任何匹配了正則規則的檔案。需要注意的是被忽略的那些檔案不允許有任何 import, require, defined或者任何其他的importing機制呼叫。這可以大大提高ewbpack build的效能,特別是大的庫檔案。再比如,你如果只有dist/xxxlib.min.js檔案的時候,就可以派上用場了。
noParse: /jquery|lodash/ // since webpack 3.0.0 noParse: function(content) { return /jquery|lodash/.test(content); }
webpack dev server訪問時出現Invalid Host header
devServer: { compress: true, disableHostCheck: true, // That solved it }