通過閱讀這篇文章,可以學習到如何使用DefinePlugin外掛使得前端專案更加工程化,說清晰點就是如何使用這個外掛,在編譯階段根據NODE_ENV自動切換配置檔案,提升前端開發效率。
- DefinePlugin的正確用法
- 如何使用DefinePlugin新增配置檔案,構建期間自動檢測環境變化,也就是如何根據NODE_ENV引入配置檔案?
DefinePlugin的正確用法
DefinePlugin中的每個鍵,是一個識別符號或者通過.
作為多個識別符號。
- 如果value是一個字串,它將會被當做code片段
- 如果value不是字串,它將會被stringify(包括函式)
- 如果value是一個物件,則所有key的定義方式相同。
- 如果key有
typeof
字首,它只是對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引入配置檔案?
情景:開發階段的介面地址往往與生產階段的介面地址是不一致的。例如開發時是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定義的名字直接引用,否則會修改失敗。