從 Babel Preset 和 Eslint Config 看配置的繼承和重寫
繼承和重寫是面向對象編程語言中的概念,是指一個類擴展自父類,并且重新實現了其中一些屬性、方法。這種思想不只是在編程語言中會用到,在配置文件中也有廣泛的應用。
本文我們分別從 babel 和 eslint 的配置文件來重新審視一下繼承和重寫。
Javascript 中的繼承和重寫
我們定義一個 Person 類,它有 eat、sleep、getSkills 3 個方法。
- class Person {
- eat(){}
- sleep() {}
- getSkills() {}
- }
然后定義一個 Guang 類,繼承自 Person,重寫 getSkills 方法。
- class Guang extends Person {
- getSkills() {
- return ['babel', 'eslint', 'vscode', 'node.js']
- }
- }
創建 Guang 的實例對象,這個對象就有 eat、sleep 方法,并且有重寫后的 getSkills 方法了。
這是一種重要的語言特性,Javascript 中是通過原型鏈實現的。
babel 配置中的繼承和重寫
babel 是微內核架構,所有的代碼轉換都是通過插件來完成的。es2015 需要指定一堆插件、es2016 也要指定一堆插件,為了簡化這些插件的配置,eslint 支持把一系列插件封裝成一個 preset,在配置文件中指定 preset 的方式來引入具體的插件。
于是 babel6 就有了 preset-es2015、preset-es2016 等 preset,后來 babel7 還支持了指定目標環境來動態指定一系列插件的 preset-env。
preset 就是把一些插件封裝到一起。比如:
- module.exports = function() {
- return {
- plugins: ["pluginA", "pluginB", "pluginC"]
- };
- };
而且 preset 里也可以繼承 preset:
- module.exports = function() {
- return {
- preset: ['presetA']
- plugins: ["pluginA", "pluginB", "pluginC"]
- };
- };
這就像 Javascript 里面 C 繼承了 B,B 繼承了 A 一樣,而且配置里還是多繼承的。
babel 插件生效的順序是先 plugin 后 preset,plugin 從左到右,preset 從右到左,這樣的生效順序使得配置里的插件是可以覆蓋 preset 里面插件的配置的,也就是重寫。
除了整體的插件的 override 以外,babel 還支持了文件級別、環境級別的 override:
文件級別的重寫:
在配置中可以設置對什么文件重寫什么配置:
- overrides: [
- {
- test: "./xxx.js",
- plugins: ['pluginX']
- }
- ]
當編譯這個文件的時候,就會把這些 option 合并到主 option 中。
比如 ts 和 js 需要的 plugin、preset 和其他配置都不一樣,通過 override 就可以分別指定。
環境級別的重寫:
當文件級別的配置重寫還不夠,有時候開發環境和生產環境也要使用不同的插件等,所以 babel 還支持了環境級別的重寫:
- envName: 'development',
- env: {
- development: {
- plugins: ['pluginA']
- },
- production: {
- plugins: ['pluginB']
- }
- }
通過 envName 來啟用不同的不同環境的配置,合并到主配置中。
這個 envName 其實不需要設置,默認是 process.env.BABEL_ENV 或者 process.env.NODE_ENV 的值。
可以看到,babel 支持了把插件封裝成 preset,preset 和 preset 之間還可以繼承,因為 生效順序是先 plugin 后 preset,所以可以達到重寫的目的。而且還可以文件級別和環境級別的重寫,分別通過 overrides 和 env 的配置。
eslint 配置中的繼承和重寫
eslint 的配置同樣支持封裝,不過不叫 preset,而叫 sharable config。因為 babel 的 preset 更多是為了簡化配置的,而 eslint 的 config 的目的不是簡化配置,而是共享配置,所以叫做 sharable config。
eslint 中可以使用 extends 來繼承一個 config:
- {
- "plugins": [
- "react"
- ],
- "extends": [
- "eslint:recommended",
- "plugin:react/recommended"
- "./aaa/.eslintrc-jsx"
- ],
- "rules": {
- "no-set-state": "off"
- }
- }
sharable config 的路徑可以通過 eslint: 來指定內置的 config,通過 plugin: 來指定插件里的 config,通過相對路徑來指定任意位置的 config。
具體的 config 就包含了各種共享的配置,而且也支持繼承自某個配置。
- module.exports = {
- rules: {
- 'no-alert': 2
- },
- extends: 'myconfig/lib/defaults'
- };
這里要注意下配置的 rule 的合并規則:
如果只重寫了錯誤級別,那么 option 會繼承。
- rule: {
- ruleA: ['error'], //只重寫錯誤級別,option 會繼承
- ruleB: ['warn', 'aaa']//錯誤級別和 option 都重寫
- }
除了整體配置的重寫之外,也同樣支持文件級別的重寫:
- "overrides": [
- {
- "files": ["**/*.md/*.js"],
- "rules": {
- "strict": "off"
- }
- }
- ]
這樣就可以在 lint 不同文件的時候使用不同的 rule,比如 ts 和 js 就需要用不同的 rule。
eslint 里有環境級別的重寫么?
沒有。babel 有環境級別的配置重寫是因為是需要生成代碼的,不同環境生成的代碼可能要有些區別。而 eslint 并不需要生成代碼,只是對源碼的 lint,所以不需要環境級別的配置重寫。
eslint 也有 env 配置,但是和 babel 的 env 不同:
- "env": {
- "es6": true
- }
eslint 的 env 配置是指定運行環境的,babel 的 env 配置是指定不同環境要重寫的配置的,兩者是不同的作用。
可以看到,eslint 支持了把配置封裝成 sharable config,config 和 config 之間還可以通過 extends 繼承,而且還支持通過 overrides 指定文件級別的重寫,但是不需要支持環境級別的重寫。
總結
繼承和重寫是一種常見的思想,不只是在編程語言的語法中,在配置文件中也有很多應用。
babel 和 eslint 都支持把一部分配置進行封裝,達到復用和簡化配置的目的,但是 babel 中叫 preset,eslint 中叫 sharable config,因為一個主要是為了簡化配置,一個主要是為了共享。
除了整體配置的重寫之外,babel 還支持文件級別的重寫(overrides)和環境級別的重寫(env),eslint 中支持文件級別的重寫(overrides)。
繼承和重寫是一種思想,不只是體現在編程語言的語法中,在配置文件領域也有很多應用。