實用webpack外掛之DefinePlugin

通過閱讀這篇文章,可以學習到如何使用DefinePlugin外掛使得前端專案更加工程化,說清晰點就是如何使用這個外掛,在編譯階段根據NODE_ENV自動切換配置檔案,提升前端開發效率。
- DefinePlugin的正確用法
- 如何使用DefinePlugin新增配置檔案,構建期間自動檢測環境變化,也就是如何根據NODE_ENV引入配置檔案?
DefinePlugin的正確用法
DefinePlugin中的每個鍵,是一個識別符號或者通過 .
作為多個識別符號。
typeof
這些值將內聯到程式碼中,壓縮減少冗餘。
new webpack.DefinePlugin({ PRODUCTION: JSON.stringify(true), VERSION: JSON.stringify('5fa3b9'), BROWSER_SUPPORTS_HTML5: true, TWO: '1+1', 'typeof window': JSON.stringify('object'), 'process.env': { NODE_ENV: JSON.stringify(process.env.NODE_ENV) } }); 複製程式碼
console.log('Running App version' + VERSION); 複製程式碼
plugin不是直接的文字值替換,它的值在字串內部必須包括 實際引用 。典型的情況是用雙引號或者JSON.stringify()進行引用,'"production"',JSON.stringify('production')。
重點:在vue-cli建立的專案中,凡是src下的檔案,都可以訪問到VERSION這個變數,例如main.js,App.vue等等
我們現在看一下上面的幾種型別的key值,在程式碼中的輸出。
console.log(PRODUCTION, VERSION, BROWSER_SUPPORTS_HTML5, TWO, typeof window, process.env); 複製程式碼
PRODUCTION: true, VERSION: "5fa3b9", BROWSER_SUPPORTS_HTML5: true, TWO: 2, typeof window: "object", process.env: {NODE_ENV: "development"}, 複製程式碼
在程式碼中,我們一般會有以下幾種用途:
- 根據process.env.NODE_ENV區分環境
- 引入配置檔案
- 根據NODE_ENV引入配置檔案 (這個很重要,後面會講到)
Feature Flag
可以控制新特性和實驗特性的開關。
new webpack.DefinePlugin({ 'NICE_FEATURE': JSON.stringify(true), 'EXPERIMENTAL': JSON.stringify(false), }) 複製程式碼
process.env.NODE_ENV的正確配置方式是什麼?
process: { env: { NODE_ENV: JSON.stringify('production') } } 複製程式碼
評價: 非常不好,會overwrite整個process物件,僅僅保留新的NODE_ENV,破壞程序。 原始的process物件包含如下內容 ,包含了當前程序的很多資訊。
process { title: 'node', version: 'v8.11.2', moduleLoadList: [ 'Binding contextify',], versions: { http_parser: '2.8.0'}, arch: 'x64', platform: 'darwin', release: { name: 'node' }, argv: [ '/usr/local/bin/node' ], execArgv: [], env: { TERM: 'xterm-256color'}, pid: 14027, features: { debug: false}, ppid: 14020, execPath: '/usr/local/bin/node', debugPort: 9229, _startProfilerIdleNotifier: [Function: _startProfilerIdleNotifier], _stopProfilerIdleNotifier: [Function: _stopProfilerIdleNotifier], _getActiveRequests: [Function: _getActiveRequests], _getActiveHandles: [Function: _getActiveHandles], reallyExit: [Function: reallyExit], abort: [Function: abort], chdir: [Function: chdir], cwd: [Function: cwd], umask: [Function: umask], getuid: [Function: getuid], geteuid: [Function: geteuid], setuid: [Function: setuid], seteuid: [Function: seteuid], setgid: [Function: setgid], setegid: [Function: setegid], getgid: [Function: getgid], getegid: [Function: getegid], getgroups: [Function: getgroups], setgroups: [Function: setgroups], initgroups: [Function: initgroups], _kill: [Function: _kill], _debugProcess: [Function: _debugProcess], _debugPause: [Function: _debugPause], _debugEnd: [Function: _debugEnd], hrtime: [Function: hrtime], cpuUsage: [Function: cpuUsage], dlopen: [Function: dlopen], uptime: [Function: uptime], memoryUsage: [Function: memoryUsage], binding: [Function: binding], _linkedBinding: [Function: _linkedBinding], _events: { newListener: [Function], removeListener: [Function], warning: [Function], SIGWINCH: [ [Function], [Function] ] }, _rawDebug: [Function], _eventsCount: 4, domain: [Getter/Setter], _maxListeners: undefined, _fatalException: [Function], _exiting: false, assert: [Function], config: {}, emitWarning: [Function], nextTick: [Function: nextTick], _tickCallback: [Function: _tickDomainCallback], _tickDomainCallback: [Function: _tickDomainCallback], stdout: [Getter], stderr: [Getter], stdin: [Getter], openStdin: [Function], exit: [Function], kill: [Function], _immediateCallback: [Function: processImmediate], argv0: 'node' } 複製程式碼
'process.env': { NODE_ENV: JSON.stringify('production') } 複製程式碼
評價: 不好,會overwrite整個process.env物件,破壞程序環境,導致破壞相容性。 原始的process.env物件包含如下內容 ,包含了當前程序的很多資訊。
{ TERM: 'xterm-256color', SHELL: '/bin/bash', TMPDIR: '/var/folders/lw/rl5nyyrn4lb0rrpspv4szc3c0000gn/T/', Apple_PubSub_Socket_Render: '/private/tmp/com.apple.launchd.dEPuHtiDsx/Render', USER: 'frank', SSH_AUTH_SOCK: '/private/tmp/com.apple.launchd.MRVOOE7lpI/Listeners', __CF_USER_TEXT_ENCODING: '0x1F5:0x19:0x34', PATH: '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/Wireshark.app/Contents/MacOS', PWD: '/Users/frank/Desktop/corporation/weidian-crm', XPC_FLAGS: '0x0', XPC_SERVICE_NAME: '0', SHLVL: '1', HOME: '/Users/frank', LOGNAME: 'frank', LC_CTYPE: 'zh_CN.UTF-8', _: '/usr/local/bin/node' } 複製程式碼
'process.env.NODE_ENV': JSON.stringify('production') 複製程式碼
評價:好。因為僅僅對NODE_ENV值進行修改,不會破壞完整程序,也不會破壞相容性。
如何使用DefinePlugin新增配置檔案,構建期間自動檢測環境變化,也就是如何根據NODE_ENV引入配置檔案?
情景:開發階段的介面地址往往與生產階段的介面地址是不一致的。 ofollow,noindex">例如開發時是development.foo.com , 而生產時是production.foo.com ,如果需要打包釋出,那麼需要手動去替換域名或者是一個分支維護一個專門的配置檔案,這兩種方式是非常笨重的。
- 手動替換 檔案效率低下,每次在development和production見切換都需要進行配置檔案的更新,容易出錯
- 配置檔案 相對手動替換高階一些,但是不能一次性檢視development和production的全部配置資訊,需要在分支間切換,效率低下,且不適用於多種環境的配置
- webpack.DefinePlugin() 全域性配置檔案,自動檢測環境變化,效率高效。
webpack的DefinePlugin正是為我們解決這樣一個問題,它維護一個全域性的配置檔案,在編譯期間會自動檢測process.env.NODE_ENV,根據當前的環境變數去替換我們的介面域名。
下面我將以一個例項來介紹如何正確使用webpack.DefinePlugin。
/config/api.js
const NODE_ENV = process.env.NODE_ENV; const config = { production: { FOO_API: 'production.foo.api.com', BAR_API: 'production.bar.api.com', BAZ_API: 'production.baz.api.com', }, development: { FOO_API: 'development.foo.api.com', BAR_API: 'development.bar.api.com', BAZ_API: 'development.baz.api.com', }, test: { FOO_API: 'test.foo.api.com', BAR_API: 'test.bar.api.com', BAZ_API: 'test.baz.api.com', } } module.exports = config[NODE_ENV]; 複製程式碼
webpack.dev.conf.js/webpack.prod.conf.js/webpack.test.conf.js
const apiConfig = require('./config/api'); const webpackConfig = { plugins: [ new webpack.DefinePlugin({ API_CONFIG: JSON.stringify(apiConfig); }) ] } ... 複製程式碼
custom.component.vue
<template> ... </template> <script> // 這裡也可以訪問到API_CONFIG export default { // 這裡無論是data函式,methods物件,computed物件,watch物件,都可以訪問到API_CONFIG; data() { return { fooApi: API_CONFIG.FOO_API, user:{ id: '', name: '', }, hash: '', } }, computed: { userAvator() { return `${API_CONFIG.BAR_API}?id=${user.id}&name=${user.name}` } }, methods: { uploadImage() { api.uploadImage({user: `${API_CONFIG.BAZ}\${hash}`}) .then(()=>{}) .catch(()=>{}) } } } </script> 複製程式碼
上述僅僅適用於vue-cli2.0時代,vue-cli3.0引入了webpack-chain,配置方式大大不同,下文將給出示例。
如何在vue.config.js中,使用使用DefinePlugin新增配置檔案,構建期間自動檢測環境變化,也就是如何根據NODE_ENV引入配置檔案?
vue.config.js
const apiConfig = require('./config/api'); module.exports = { chainWebpack: config => { config .plugin('define') .tap(args => { args[0].API_CONFIG = JSON.stringify(apiConfig) return args }) } } 複製程式碼
需要注意的是,在vue-cli3.0中,我們不能直接SET NODE_ENV=production或者EXPORT NODE_ENV=production。 因為vue-cli-servive有3種模式,serve預設為development,build為production,若想修改vue-cli-service包中的NODE_ENV,需要通過vue-cli-service serve --mode production進行切換。 就像下面這樣:
{ "scripts": { "dev": "vue-cli-service serve", // mode預設為development "production": "vue-cli-service serve --mode production", }, } 複製程式碼
注意:我們只能在development, production或者test 3個模式下進行切換,不能引入類似preproduction之類的自定義node環境,但是實際上這3個環境已經足以滿足大多數的開發情況。
為什麼vue-cli 3.0中的DefinePlugin可以用config.plugin('define')修改入參?
在 原始碼檔案base.js 中,有下面的程式碼:
webpackConfig .plugin('define') .use(require('webpack/lib/DefinePlugin'), [ resolveClientEnv(options) ]) 複製程式碼
這一點很關鍵! 我們在vue.config.js中拿到的config.plugin('define'),實際上時vue-service內部建立的webpack.DefinePlugin例項的引用 ! 明確了這一點,我們在以後增強webpack預設外掛配置時,需要先到vue-service的原始碼中尋找一番,看看有沒有對應plugin的引用,若有,必須根據vue-service定義的名字直接引用,否則會修改失敗。