本文是對某開源的項目webpack5 + react + typescript
項目地址逐行代碼作分析,解剖一個成熟的環境全部配置的意義,理清一些常見的問題,好比javascript
文件中的 import
轉es5
被 webpack
編譯成了什麼?webpack的分包配置splitChunks咋用? 你真的理解其選項值chunks
的值async
或者initial
或者all
是什麼意思嗎,這個可對分包優化相當重要啊!爲何package.json
沒有依賴的包,在node_modules
下面會出現,npm install
包是以什麼結構安裝npm包呢?css
babel/core
有什麼用,它跟babel-loader
的區別, babelrc
文件中配置項presets
和plugin
的區別是什麼,babelrc
經常使用設置項知道多少,這個不清楚?那項目代碼校驗和格式化用到editorConfig、prettier、eslint,stylelint
他們的關係和區別是什麼?如何配置防止它們衝突,好比eslint
也有css
校驗,怎麼讓stylelint
跟它不起衝突,這些你要晉升爲前端主管怎麼能內心沒數?html
若是你用的vscode
,如何在工做區配置ctrl+s
自動保存,讓你的js和css文件自動格式化,並配置爲prettier
格式化,webpack5
和4
的配置中的變化、等等。。。前端
以上提到的知識點對咱們深刻了解項目環境搭建
很是重要, 你的項目你來時通常環境都是搭建好的,試過從0本身搭建不?是否是抄別人的配置,都一頭霧水,徹底不知道這些配置項時啥意思呢?vue
如今!本篇本章專解這個問題!廢話少說,java
咱們先從package.json
提及,裏面的每一行代碼是什麼意思。node
package.json
裏面有不少有趣的內容,咱們先從依賴包提及,解釋這個項目中,下面的依賴包分別有什麼用。react
"devDependencies": {
"@babel/core": "^7.13.13",
"@babel/plugin-transform-runtime": "^7.13.10",
"@babel/preset-env": "^7.13.12",
"@babel/preset-react": "^7.13.13",
"@babel/preset-typescript": "^7.13.0",
"@commitlint/cli": "^12.0.1",
"@commitlint/config-conventional": "^12.0.1",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.3",
"@types/webpack-env": "^1.16.0",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"babel-loader": "^8.2.2",
"chalk": "^4.1.0",
"clean-webpack-plugin": "^3.0.0",
"conventional-changelog-cli": "^2.1.1",
"copy-webpack-plugin": "^8.1.0",
"cross-env": "^7.0.3",
"css-loader": "^5.2.0",
"css-minimizer-webpack-plugin": "^1.3.0",
"detect-port-alt": "^1.1.6",
"error-overlay-webpack-plugin": "^0.4.2",
"eslint": "^7.22.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^8.1.0",
"eslint-import-resolver-typescript": "^2.4.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-react": "^7.23.1",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-unicorn": "^29.0.0",
"fork-ts-checker-webpack-plugin": "^6.2.0",
"html-webpack-plugin": "^5.3.1",
"husky": "^4.3.8",
"ip": "^1.1.5",
"is-root": "^2.1.0",
"lint-staged": "^10.5.4",
"mini-css-extract-plugin": "^1.4.0",
"node-sass": "^5.0.0",
"postcss": "^8.2.8",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-loader": "^5.2.0",
"postcss-preset-env": "^6.7.0",
"prettier": "^2.2.1",
"sass-loader": "^11.0.1",
"style-loader": "^2.0.0",
"stylelint": "^13.12.0",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^21.0.0",
"stylelint-declaration-block-no-ignored-properties": "^2.3.0",
"stylelint-order": "^4.1.0",
"stylelint-scss": "^3.19.0",
"terser-webpack-plugin": "^5.1.1",
"typescript": "^4.2.3",
"webpack": "^5.37.1",
"webpack-bundle-analyzer": "^4.4.0",
"webpack-cli": "^4.5.0",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.7.3",
"webpackbar": "^5.0.0-3"
},
"dependencies": {
"@babel/runtime-corejs3": "^7.13.10",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
複製代碼
babel
的功能在於「代碼轉譯」
,具體一點,即將目標代碼轉譯爲可以符合指望語法規範的代碼。在轉譯的 過程當中,babel
內部經歷了「解析 - 轉換 - 生成」
三個步驟。而 @babel/core
這個庫則負責「解析」
,具體的「轉換」
和「生成」
步驟則交給各類插件(plugin)和預設(preset)來完成。linux
你能夠從@babel/core
本身的依賴裏看到其中有三個包,叫@babel/generator
(將ast生成代碼)、 @babel/parser
(將源代碼轉換爲AST)、@babel/traverse
(轉換AST),有這三個包,就能轉換你的代碼,案例以下:webpack
import { parse } from '@babel/parser';
import traverse from '@babel/traverse';
import generate from '@babel/generator';
const code = 'const n = 1';
// 將源代碼轉換爲AST
const ast = parse(code);
// 轉換AST
traverse(ast, {
enter(path) {
// in this example change all the variable `n` to `x`
if (path.isIdentifier({ name: 'n' })) {
path.node.name = 'x';
}
},
});
// 生成代碼 <- ast
const output = generate(ast, code);
console.log(output.code); // 'const x = 1;'
複製代碼
這應該很是清楚的瞭解babel/core
有什麼用了吧,至於說怎麼在traverse
階段改變代碼,就要用到其餘的插件了,咱們立刻說一下babel-loader
,讓你明白它跟babel/core
的區別
咱們知道webpack
須要各類loader
,這些loader
的做用就是把文件作轉化,好比babel-loader
是用來轉化js
,jsx
,ts
,tsx
文件的。
好比咱們寫的js
代碼是es6
,import xx模塊 from ‘xx模塊’
,爲了瀏覽器兼容性,咱們須要轉化爲es5
的寫法,轉譯import
,那麼這個時候就須要babel-loader
來幫忙了。
好比說一個簡單的loader
怎麼寫呢,咱們就知道babel-loader
大概是個什麼東西了,
module.exports = source => {
// source 就是加載到的文件內容
console.log(source)
return "hello ~" // 返回一個字符串
}
複製代碼
上面咱們把任何加載到的文件內容轉化爲一個字符串,也就是loader
無非是加工讀到的文件,因此babel-loader
就是讀取對應的jsx?|tsx?
文件,而後加工後返回而已
ts
文件的。目前 TypeScript
的編譯有兩種方式。一種是使用 TypeScript
自家的編譯器 typescript
編譯(簡稱 TS 編譯器),一種就是使用 Babel + @babel/preset-typescript
編譯。
其中最好的選擇就是使用Babel + @babel/preset-typescript
,主要緣由是:
Babel
可以指定須要編譯的瀏覽器環境。這一點 TS 編譯器是不支持的。在babelrc
文件裏能夠設置編譯的target
屬性(在preset-env
插件上設置)爲好比"targets": {
"browsers": ["last 2 versions", "safari >= 7"], // 配置safari的版本大於7的語法才轉譯
"node": "6.10" // node版本支持到6.10
複製代碼
TS
編譯器在編譯過程當中進行類型檢查,類型檢查是須要時間的,而 babel
不作類型檢查,編譯速度就更快@babel/preset-react: 主要是編譯jsx
文件的,也就是解析jsx語
法的,好比說react
生成div,咱們舉一個例子,在jsx裏面是這樣的,轉換成什麼了呢?
<div></div>
複製代碼
轉化後的react
的api
const reactElement = React.createElement(
... // 標籤名稱字符串/ReactClass,
... // [元素的屬性值對對象],
... // [元素的子節點]
)
reactElement('div', null, '')
複製代碼
@babel/preset-env
將基於你的實際瀏覽器及運行環境,自動的肯定babel
插件及polyfill
,在不進行任何配置的狀況下,@babel/preset-env
所包含的插件將支持全部最新的JS特性(ES2015
,ES2016
等,不包含 stage
階段),將其轉換成ES5
代碼。例,那麼只配置 @babel/preset-env
,轉換時會拋出錯誤,須要另外安裝相應的插件。
//.babelrc
{
"presets": ["@babel/preset-env"]
}
複製代碼
注意:@babel/preset-env
會根據你配置的目標環境,生成插件列表來編譯。Babel
官方建議咱們把 targets
的內容保存到 .browserslistrc
文件中 或者 package.json
裏增長一個browserslit
節點,否則除了babel
外,其餘的工具,例如browserslist
、post-css
等沒法從 babel
配置文件裏讀取配置
若是你不是要兼容全部的瀏覽器和環境,推薦你指定目標環境,這樣你的編譯代碼可以保持最小。
具體用法咱們會在將babelrc
文件配置(babel
的配置文件)的時候詳細說明。
爲何咱們須要它,咱們來看看@babel/prest-env
編譯完js文件後,會有哪些問題
好比咱們使用字符串的inclues語法(es5中並不支持它,須要轉譯), 例如 Array.from
等靜態方法,直接在 global.Array
上添加;對於例如 includes 等實例方法,直接在global.Array.prototype
上添加。這樣直接修改了全局變量的原型。
babel
轉譯 syntax 時,有時候會使用一些輔助的函數來幫忙轉,好比:
class 語法中,babel 自定義了 _classCallCheck
這個函數來輔助;typeof
則是直接重寫了一遍,自定義了 _typeof 這個函數來輔助
。這些函數叫作 helpers。每一個項目文件都寫無心是不合理的。
做用是將 helper
(輔助函數) 和 polyfill
(不修改全局變量原型的靜態方法等) 都改成從一個統一的地方引入,而且引入的對象和全局變量是徹底隔離的。
具體配置不詳細說明了,到後面講babelrc
文件的的時候說。
上面咱們看到了@babel/prest-env
帶來的問題,這兩個問題@babel/plugin-transform-runtime能夠解決,那@babel/runtime-corejs
又是個什麼東西呢?
其中 @babel/plugin-transform-runtime
的做用是轉譯代碼,轉譯後的代碼中可能會引入 @babel/runtime-corejs
裏面的模塊,也就是說具體轉譯代碼的函數是單獨在另外一個包裏,就是@babel/runtime-corejs
裏面
@types/react、@types/react-dom
這兩個是react的typescript類型定義
@types/webpack-env
是webpack的typescript類型定義
eslint:是一個插件化而且可配置的 JavaScript
語法規則和代碼風格的檢查工具。這個就很少說了,你們都知道吧,不用eslint
的前端項目應該不多。
eslint-config-airbnb:Airbnb
的eslint規則的標準,它依賴eslint, eslint-plugin-import, eslint-plugin-react, and eslint-plugin-jsx-a11y
等插件,而且對各個插件的版本有所要求。
eslint-config-prettier:prettier
是一個代碼格式化工具,好比說規範項目都使用單引號,仍是雙引號。並且,Prettier
還給予了一部分配置項,能夠經過 .prettierrc
文件修改。
因此至關於 Prettier
接管代碼格式的問題,而使用 Prettier + ESLint
就完徹底全解決了代碼格式和代碼語法規則校驗的問題。
但實際上使用起來配置有些小麻煩,但也不是什麼大問題。由於 Prettier
和 ESLint
一塊兒使用的時候會有衝突,咱們須要使用 eslint-config-prettier
來關掉 (disable) 全部和 Prettier
衝突的 ESLint 的配置,
eslint-plugin-prettier
將 prettier 的 rules 以插件的形式加入到 ESLint 裏面方法就是在 .eslintrc 裏面將 prettier 設爲最後一個 extends
// .eslintrc
{
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error"
}
}
複製代碼
將上面兩個步驟和在一塊兒就是下面的配置,也是官方的推薦配置
// .eslintrc
{
"extends": ["plugin:prettier/recommended"]
}
複製代碼
es6
的import
規則,若是增長import plugin
,在咱們使用webpack
的時候,若是你配置了resolve.config.js
的alias
,那麼咱們但願import plugin的校驗規則會從這裏取模塊的路徑,此時須要配置,注意,此時同時要下載eslint-import-resolver-webpack
插件才能像下面同樣設置「rules」: {},
/** 這裏傳入webpack並非import插件能識別webpack, * 並且經過npm安裝了「eslint-import-resolver-webpack」, * 「import」插件經過「eslint-import-resolver-」+「webpack」找到該插件並使用, * 就能解析webpack配置項。使用裏面的參數。 **/
"settings": {
// 使用webpack中配置的resolve路徑
"import/resolver": "webpack"
}
複製代碼
eslint-import-resolver-typescript:它也是「eslint-import-resolver-」
家族的一員,它的做用是
import/require
擴展名爲 .ts/.tsx 的文件 tsconfig.json
中定義的paths路徑@types/*
定義而不是普通的 .jseslint-plugin-jsx-a11y: 該插件爲你的 JSX
中的無障礙問題提供了 AST
的語法檢測反饋。
eslint-plugin-react: 一些 react
的 eslint
的 rules
規範
eslint-plugin-react-hooks:檢測react hooks的一些語法規範,並提供相應的rules
postcss: 是一個使用JavaScript插件來轉換CSS
的工具。
PostCSS
自己很小,其只包含CSS
解析器,操做CSS
節點樹的API
,source map
,以及一個節點樹字符串化工具,其它功能都是經過插件來實現的,好比說插件有
一、添加瀏覽器內核前綴的
二、有檢測css
代碼的工具等等
postcss-flexbugs-fixes: 修復在一些瀏覽器上flex佈局的bug,好比說
----|標準| flex: 1 flex: 1 1 0% flex: 1 0 0px flex: auto flex: 1 1 auto flex: 1 0 auto
縮寫聲明 | 標準轉義 | IE10轉義 |
---|---|---|
(no flex declaration) | flex: 0 1 auto | flex: 0 0 auto |
flex: 1 | flex: 1 1 0% | flex: 1 0 0px |
flex: auto | flex: 1 1 auto | flex: 1 0 auto |
postcss-loader:loader
的功能在上面已經說明,這個loader
是postcss
用來改變css
代碼的loader
postcss-preset-env:這個插件主要是集成了(有了它不用下載autoprefixer插件)
autoprefixer:用於解析 CSS
並使用 Can I Use
中的值向 CSS
規則添加供應商前綴
style-resoures-loader:這個插件比較重要,即便這個項目沒有用,我也建議你們項目用上。它的做用就是避免重複在每一個樣式文件中@import
導入,在各個css
文件中可以直接使用變量和公共的樣式。
webpack:這個不用描述了吧。。。
webpack-cli:
webpack
的命令行工具,在 4.x
版本以後再也不做爲 webpack
的依賴了,咱們使用時須要單獨安裝這個工具。webpack-bundle-analyzer: webpack
打包體積分析工具,會讓咱們知道打包後的文件分別是由哪些文件組成,而且體積是多少,是一款優化分析打包文件的工具
webpack-dev-server:是一個小型的Node.js Express
服務器,它使用webpack-dev-middleware
來服務於webpack的包,除此自外,它還有一個經過Sock.js
來鏈接到服務器的微型運行時
webpack-merge:
webpack.common.js
(後面兩個js文件共同的內容抽離出來),webpack.pro.js
(生產環境獨有的內容),webpack.dev.js
(開發環境獨有的內容)。webpack.common.js
和webpack.pro.js
變爲生產環境的內容,同理common
和dev
也是如此。咱們就須要webpack-merge
方法了。它的做用以下const merge = require("webpack-merge");
merge(
{a : [1],b:5,c:20},
{a : [2],b:10, d: 421}
)
//合併後的結果
{a : [1,2] ,b :10 , c : 20, d : 421}
複製代碼
從上面的案例,咱們能夠看出來, 數組內容會合並,基礎類型的值會被覆蓋,這比較符合咱們webpack.common.js
有一些plugins:[],webpack.pro.js
也有一些plugins是合併的須要,而不是覆蓋。
stylelint:stylelint
用於樣式規範檢查與修復,支持 .css .scss .less .sss
stylelint-config-prettier:關閉全部沒必要要的或可能與 Prettier
衝突的規則。
stylelint-config-rational-order:它對你的css
樣式排序會有要求,具體爲
Positioning -- 定位
Box Model -- 盒模型
Typography -- 版式
Visual -- 可見性(顯示和隱藏)
Animation -- 動畫
Misc -- 其它雜項
.declaration-order {
/* 1.Positioning 位置屬性 */
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 10;
/* 2.Box Model 盒子屬性 */
display: block;
float: right;
width: 100px;
height: 100px;
margin: 10px;
padding: 10px;
/* 3.Typography 文字屬性 */
color: #888;
font: normal 16px Helvetica, sans-serif;
line-height: 1.3;
text-align: center;
/* 4.Visual 視覺屬性 */
background-color: #eee;
border: 1px solid #888;
border-radius: 4px;
opacity: 1;
/* 5.Animation Misc 其餘 */
transition: all 1s;
user-select: none;
}
複製代碼
你不按上面的順序寫css
的話,會警告或者報錯。
stylelint-order:這個實現的功能也是排序,不過它跟上面的插件的區別是,它按照字母(英文是alpha sort
)排序,因此兩個插件要配合使用。
stylelint-config-standard:該風格是 Stylelint
的維護者汲取了 GitHub、Google、Airbnb
多家之長生成的一套css風格規則。
stylelint-declaration-block-no-ignored-properties:這個插件的做用是警告那些不起做用的屬性。好比說你設置了display:inline,width: 200px
,其實這裏的width
是不起做用的,此時這個插件就會發出警告
打印有顏色文字的插件:用法好比說
// 控制檯打印紅色的hello
require('chalk').red('hello')
複製代碼
webpack使用的插件,通常用在production環境,用來清除文件夾用的,就是相似rm -rf ./dist
commitlint
能夠幫助咱們進行 git commit
時的 message 格式是否符合規範,conventional-changelog
能夠幫助咱們快速生成 changelog
@commitlint/config-conventional
相似 eslint 配置文件中的 extends ,它是官方推薦的 angular
風格的 commitlint 配置
在webpack中拷貝文件和文件夾
它是運行跨平臺設置和使用環境變量(Node中的環境變量)的腳本。由於在windows和linux|mac裏設置環境變量的方法不一致,好比說
// 在windows系統上,咱們使用:
"SET NODE_ENV=production && webpack --config build/webpack.config.js"
複製代碼
// 在Lunix系統和安裝並使用了bash的windows的系統上,咱們會使用:
"EXPORT NODE_ENV=production && webpack --config build/webpack.config.js"
複製代碼
webpack 4.0
之後,官方推薦使用mini-css-extract-plugin
插件來打包css文件(從css文件中提取css代碼到單獨的文件中,對css
代碼進行代碼壓縮等)
相對的,若是你不想提取css
,可使用style-loader
,將css
內嵌到html
文件裏。
使用方法和效果以下:(後面會在webpack配置文件分析裏看到),
先舉一個基礎配置的例子。 webpack.config.js
:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css'
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, 'css-loader','postcss-loader' // postcss-loader 可選
],
},{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader, 'css-loader','postcss-loader','less-loader' // postcss-loader 可選
],
}
],
},
};
複製代碼
基於以上配置
app.js
中引用了 Root,js
Root
引入了 Topics.js
Root.js
中引用樣式 main.css
Topics.js
中引用了 topics.css
。// 入口文件 app.js
import Root from './components/Root'
// Root.js
import '../styles/main.less'
import Topics from './Topics'
// Topics.js
import "../styles/topics.less"
複製代碼
這種狀況下,Topics
會和 Root
同屬一個 chunk
,因此會一塊兒都打包到 app.js
中, 結果就是 main.less 和 topics.less 會被提取到一個文件中:app.css
。而不是生成兩個 css
文件。
Asset Size Chunks Chunk Names
app.css 332 bytes 1 [emitted] app
app.js 283 KiB 1 [emitted] [big] app
複製代碼
可是,若是 Root.js
中並無直接引入 Topics
組件,而是配置了代碼分割 ,好比模塊的動態引入(也就是說你的topics模塊,是impot()動態引入的),那麼結果就不同了:
Asset Size Chunks Chunk Names
app.css 260 bytes 1 [emitted] app
app.js 281 KiB 1 [emitted] [big] app
topics.bundle.js 2.55 KiB 4 [emitted] topics
topics.css 72 bytes 4 [emitted] topics
複製代碼
由於這個時候有兩個 chunk,對應了兩個 JS 文件,因此會提取這兩個 JS 文件中的 CSS 生成對應的文件。這纔是「爲每一個包含 CSS 的 JS 文件建立一個單獨的 CSS 文件」
的真正含義。
可是,若是分割了 chunk
,仍是隻但願只生成一個 CSS
文件怎麼辦呢?也是能夠作到的。但須要藉助 Webpack 的配置 optimization.splitChunks.cacheGroups
。
先來看看配置怎麼寫的:
optimization: {
splitChunks: {
cacheGroups: {
// Extracting all CSS/less in a single file
styles: {
name: 'styles',
test: /\.(c|le)ss$/,
chunks: 'all',
enforce: true,
},
}
}
},
複製代碼
打包結果:
Asset Size Chunks Chunk Names
app.js 281 KiB 2 [emitted] [big] app
styles.bundle.js 402 bytes 0 [emitted] styles
styles.css 332 bytes 0 [emitted] styles
topics.bundle.js 2.38 KiB 5 [emitted] topics
複製代碼
繼續增強上面的配置,壓縮上面分理處的代碼, css-minimizer-webpack-plugin
是用來壓縮分離出來的css的。使用方法以下:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /.s?css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
],
},
optimization: {
minimizer: [
// For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line
// `...`,
new CssMinimizerPlugin(),
],
},
};
複製代碼
這個包用來檢測對應端口是否被佔用,好比項目裏發現啓動3000端口被佔用的話就+1,直到選擇一個不被佔用的端口(端口上限是65535)。
它提供了和 create-react-app 同樣的錯誤遮罩:
用法以下:
const ErrorOverlayPlugin = require('error-overlay-webpack-plugin')
module.exports = {
plugins: [new ErrorOverlayPlugin()],
devtool: 'cheap-module-source-map', // 'eval' is not supported by error-overlay-webpack-plugin
}
複製代碼
@typescript-eslint/parser:ESLint的解析器,用於解析typescript,從而檢查和規範Typescript代碼
@typescript-eslint/eslint-plugin:這是一個ESLint插件,包含了各種定義好的檢測Typescript代碼的規範
配置以下所示:
module.exports = {
parser: '@typescript-eslint/parser', // 定義ESLint的解析器
extends: ['plugin:@typescript-eslint/recommended'],// 定義文件繼承的子規範
plugins: ['@typescript-eslint'],// 定義了該eslint文件所依賴的插件
env:{ // 指定代碼的運行環境
browser: true,
node: true,
}
}
複製代碼
它在一個單獨的進程上運行類型檢查器,該插件在編譯之間重用抽象語法樹,並與TSLint共享這些樹。能夠經過多進程模式進行擴展,以利用最大的CPU能力。
這個插件很是經常使用,幾乎是必備的。
它的做用是:當使用 webpack
打包時,建立一個 html
文件,並把 webpack
打包後的靜態文件自動插入到這個 html 文件當中。簡單實用以下(講webpack文件時會更詳細介紹api):
{
entry: 'index.js',
output: {
path: __dirname + '/dist',
filename: 'bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
title: 'My App',
filename: 'assets/admin.html' // 在 output.path 目錄下生成 assets/admin.html 文件
})
]
}
複製代碼
husky是一個npm包,安裝後,能夠很方便的在package.json配置git hook 腳本 。
好比,在 package.json 內配置如
"scripts": {
"lint": "eslint src"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint"
}
},
複製代碼
那麼,在後續的每一次git commit
以前,都會執行一次對應的 hook 腳本npm run lint
。其餘hook同理.
若是咱們 想對git 緩存區最新改動過的文件進行以上的格式化和 lint
規則校驗,這就須要 lint-staged
了 。
以下:
{
"husky": {
"hooks": {
"pre-commit": "lint-staged",
}
},
"lint-staged": {
// 首先,咱們會對暫存區後綴爲 `.ts .tsx .js` 的文件進行 eslint 校驗,
// --config 的做用是指定配置文件。
"*.{ts,tsx,js}": [
"eslint --config .eslintrc.js"
],
// 同理 是stylelint的校驗
"*.{css,less,scss}": [
"stylelint --config .stylelintrc.js"
],
// prettier格式化
"*.{ts,tsx,js,json,html,yml,css,less,scss,md}": [
"prettier --write"
]
},
}
複製代碼
這裏沒有添加 --fix
來自動修復不符合規則的代碼,由於自動修復的內容對咱們不透明,這樣不太好。
是一個使用 terser
壓縮js
的webpack
插件。
若是你使用的是 webpack v5
或以上版本,你不須要安裝這個插件。webpack v5
自帶最新的 terser-webpack-plugin
。若是使用 webpack v4
,則必須安裝 terser-webpack-plugin v4
的版本。
簡易用法以下,詳細介紹留到後面webpack配置文件詳解
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [new TerserPlugin(
parallel: true // 多線程
)],
},
};
複製代碼
{
"main": "index.js",
"scripts": {
"start": "cross-env NODE_ENV=development node scripts/server",
"build": "cross-env NODE_ENV=production webpack --config ./scripts/config/webpack.prod.js",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"lint": "npm run lint-eslint && npm run lint-stylelint",
"lint-eslint": "eslint -c .eslintrc.js --ext .ts,.tsx,.js src",
"lint-stylelint": "stylelint --config .stylelintrc.js src/**/*.{less,css,scss}"
},
"browserslist": [">0.2%", "not dead", "ie >= 9", "not op_mini all"],
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint --config .commitlintrc.js -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{ts,tsx,js}": ["eslint --config .eslintrc.js"],
"*.{css,less,scss}": ["stylelint --config .stylelintrc.js"],
"*.{ts,tsx,js,json,html,yml,css,less,scss,md}": ["prettier --write"]
}
}
複製代碼
這裏的main
須要跟一些其它字段來一塊兒比較。好比browser,module,main
三個字段均可以出如今package.json
中,它們有什麼區別呢?
咱們直接說結論,具體詳細分析,詳情參考這篇文章開發插件package.json在webpack構建中的表現
根據上面的行爲總結,咱們在開發插件的時候,須要考慮插件是提供給web
環境仍是node
環境,若是都涉及到且存在區別,就要明確指出 browser、module
字段。若是沒有任何區別的話,使用 main
入口足以
這個文件對於使用vscode的用戶比較重要,有一些設置很是棒,好比點擊ctrl+s自動格式化你的文件,設置以下:
"editor.formatOnSave": true, // 自動格式化代碼,咱們使用的是prettier
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true, // 保存自動修復 eslint報錯(有些報錯必須手動修復)
"source.fixAll.stylelint": true // 保存自動修復 stylelint報錯,也就是css報錯
}
複製代碼
下圖是具體的settings文件,逐一註釋其中的做用
{
"css.validate": false, // 禁用vscode自己的css校驗功能
"less.validate": false, // 禁用vscode自己的less校驗功能
"scss.validate": false, // 禁用vscode自己的scss校驗功能
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], // eslint 校驗的文件格式
"search.exclude": { // 搜索文件時排除的文件夾
"**/node_modules": true,
"dist": true,
"build": true
},
"editor.formatOnSave": true, // 保存時,自動格式化
"editor.codeActionsOnSave": { // 保存時自動格式化eslint的規則和stylint的規則
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
"[javascript]": { // 底下相似是指校驗js,jsx,ts,tsx的校驗器是prettier,而不是vscode默認的
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
複製代碼
上面的保存時自動格式化eslint的規則和stylint的規則,須要注意的是,有些規則是必須手動修改的,不會自動保存格式化。
下面的presets和plugins的區別是,
presets是一些預設,插件的對應名字是babel-preset-xxx
。Babel
插件通常儘量拆成小的力度,開發者能夠按需引進。可是一個一個引進有時候很麻煩,能不能把一些經常使用的插件打成一個包給咱們用呢,這就是presets
的做用和。
plugins
就是一個一個的插件集合,你要配特定的功能就能夠加入到plugins中
如下的全部插件以前都介紹過,能夠試着回憶一下哦
module.exports = {
presets: [
[
'@babel/preset-env', // 將基於你的實際瀏覽器及運行環境,自動的肯定babel插件及polyfill
{
useBuiltIns: 'usage', // 按需使用
modules: false, // 意思是不轉義import語法,主要是爲了tree-shaking
},
],
'@babel/preset-react', // 轉化js、jsx文件的插件集合
'@babel/preset-typescript', // 轉化ts,tsx文件的插件集合
],
plugins: [
[
'@babel/plugin-transform-runtime',// 優化polyfill的插件
{
corejs: {
version: 3,
proposals: true,
},
},
],
],
};
複製代碼
這裏詳細解釋一下@babel/preset-env
這個插件的詳細的常見使用參數,由於它很重要,是babel
轉義咱們代碼的關鍵插件:
可是這裏不建議把browsers
信息寫在eslinttc
裏面,由於可能其餘的插件也須要瀏覽器信息,最好寫在package.json
中。 例如:
"browserslist": [">0.2%", "not dead", "ie >= 9", "not op_mini all"],
複製代碼
modules屬性,若是是false,就是說導出方式是按es6 module,默認是commonjs規範
useBuiltIns:規定如何引入polyfill,好比說有些瀏覽器不支持promise,咱們須要引入polyfill去兼容這些不支持promise的瀏覽器環境
useBuiltIns: 'usage'以後,就沒必要手動在入口文件中
import '@babel/polyfill'`entry
配置項時, 根據target
中瀏覽器版本的支持,將polyfills
拆分引入,僅引入有瀏覽器不支持的polyfill
useBuiltIns: usage
或者useBuiltIns: entry
一塊兒使用時纔會生效, 確保@babel/preset-env
爲你的core-js
版本注入了正確的引入接着,咱們解釋一下在'@babel/plugin-transform-runtime'插件的配置:
['@babel/plugin-transform-runtime', { corejs: 2 }]
,指定一個數字將引入corejs
來重寫須要polyfillAPI
的helpers
,這須要使用@babel/runtime-corejs2
做爲依賴技術細節:transform-runtime
轉換器插件會作三件事:
generators/async
函數時,自動引入@babel/runtime/regenerator
(可經過regenerator選項切換)core-js
做爲helpers
,而不是假定用戶已經使用了polyfill
(可經過corejs選項切換)@babel/runtime/helpers
模塊(可經過helpers選項切換)最後,咱們要提一個問題,就是import 經過webpack轉義以後,變成了什麼樣子,咱們用案例來講。
以下是一個很是簡單的webpack編譯的模塊。
import { tmpPrint } from './tmp.js'
export function print () {
tmpPrint()
console.log('我是 num.js 的 print 方法')
}
複製代碼
會被webpack編譯爲以路徑爲key,以函數爲value的對象。
{
"./src/num.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, "print", function () { return print; });
// 加載 ./src/tmp.js 模塊
var _tmp_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/tmp.js");
function print() {
Object(_tmp_js__WEBPACK_IMPORTED_MODULE_0__["tmpPrint"])()
console.log('我是 num.js 的 print 方法')
}
//# sourceURL=webpack:///./src/num.js?");
}),
}
複製代碼
咱們接着看一下__webpack_require__.r,webpack_require.d,__webpack_require__分別是什麼:
function __webpack_require__(moduleId) {
// 全部模塊都會被緩存,若是在緩存裏就直接從緩存裏拿
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 這裏是緩存的定義,i是id的意思,l是load的意思,exports是導出的內容
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// 若是不是在緩存裏,取出模塊把module, module.exports, __webpack_require__做爲參數放入到模塊裏
// 以下的modules[moduleId]中保存的內容就至關於這塊內容:
// (function (module, __webpack_exports__, __webpack_require__) {
// "use strict";
// __webpack_require__.r(__webpack_exports__);
// __webpack_require__.d(__webpack_exports__, "print", function () { return // print; });
// 加載 ./src/tmp.js 模塊
// var _tmp_js__WEBPACK_IMPORTED_MODULE_0__ = // __webpack_require__("./src/tmp.js");
// function print() {
// Object(_tmp_js__WEBPACK_IMPORTED_MODULE_0__["tmpPrint"])()
// console.log('我是 num.js 的 print 方法')
// }
//# sourceURL=webpack:///./src/num.js?");
// }),
// }
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;
}
__webpack_require__.d = function (exports, name, getter) {
// __webpack_require__.o這個函數的意思是檢查name是不是exports的屬性
if (!__webpack_require__.o(exports, name)) {
// 若是exports本來沒有name屬性,就用defineProperty去定義name屬性
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// 這個函數就是用來標識是不是es模塊的
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
複製代碼
好了,咱們接着回頭接續分析那個src/sum.js的模塊
{
"./src/num.js":
// 你們還記得上面講require有一句
// modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 咱們能夠看到 module就等於下面函數的 module,表明這個模塊對象
// module.exports對應__webpack_exports__,也就是__webpack_exports__表明module導出的對象
// __webpack_require__對應function的 __webpack_require__參數,也就是導入模塊函數
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
// 這句話的意思把導出的exports對象標記爲esmodule
__webpack_require__.r(__webpack_exports__);
// 這句話的意思是把模塊下面的print函數,放入exports導出對象
__webpack_require__.d(__webpack_exports__, "print", function () { return print; });
// 加載 ./src/tmp.js
var _tmp_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/tmp.js");
function print() {
Object(_tmp_js__WEBPACK_IMPORTED_MODULE_0__["tmpPrint"])()
console.log('我是 num.js 的 print 方法')
}
//# sourceURL=webpack:///./src/num.js?");
}),
}
複製代碼
通過上面分析,發現最關鍵的一句就是__webpack_require__.r(webpack_exports),把導出對象標記爲esModule,若是你沒有用import,而是用commonjs的require,那麼就不會有這一句
那問題又來了,若是是表示esmodule了,有啥用啊!這部分我就不寫了,要不就是一篇專門講import和require區別的文章了。
我直接說結論了
require('./Header').default
複製代碼
const Header = require('./Header')
Header.default 表示導出的Header組件
Header.A表明導出的A
// Header.js
export default Header;
export const A=1;
複製代碼
require('./Header').A
複製代碼
export default Header
會被掛在exports的default屬性上export const A=1
,會被掛在exports的A屬性上意思是es6模塊實際上被webpack
的一套規則仍是變味了commonjs
規範而已。
上面沒看懂?不要緊的,更具體更清晰的推論,在tsconfig.js
文件的esModuleInterop參數講解中會有更清晰的解釋(這個是站在webpack編譯的角度,下面esModuleInterop參數是在ts編譯的角度,其實原理都是同樣的)。
如下比較簡單的文件,我就在文件註釋中解釋參數了
以下的rules的規範以下:rule
由name
和配置數組組成,如:'name:[0, 'always', 72]'
,數組中第一位爲level
,可選0,1,2
,0
爲disable
,1
爲warning
,2
爲error
,第二位爲應用與否,可選always|never
,第三位該rule的值,下面的值表明你的commit開頭必須是這些字段
module.exports = {
extends: ['@commitlint/config-conventional'], // 這個插件繼承的是angular團隊的提交規範
rules: {
'type-enum': [ // 解釋上面已經提過數組每一位的意思
2,
'always',
['build', 'ci', 'chore', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test'],
],
},
};
複製代碼
// 代表是最頂層的配置文件,發現設爲 true 時,纔會中止查找.editorconfig 文件
root = true
[*]
// tab 爲 hard-tabs,space 爲 soft-tabs 表示縮進符號,咱們選的空格
indent_style = space
// 設置整數表示規定每級縮進的列數和 soft-tabs 的空格數。
// 若是設定爲 tab,則會使用 tab_width 的值(若是已指定)
indent_size = 2
// 定義換行符,支持 lf、cr 和 crlf
end_of_line = lf
// 編碼格式,支持 latin一、utf-八、utf-8-bom、utf-16be 和 utf-16le,不建議使用 uft-8-bom
charset = utf-8
// 設爲 true 表示會除去換行行首的任意空白字符,false 反之
trim_trailing_whitespace = true
// 設爲 true 代表使文件以一個空白行結尾,false 反之
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
複製代碼
const OFF = 0;
const WARN = 1;
const ERROR = 2;
module.exports = {
// 要在配置文件中指定環境,請使用env鍵並經過將每一個設置爲來指定要啓用的環境true。
// 例如,如下啓用瀏覽器和Node.js環境:
// es6表示對於新的ES6全局變量,好比Set的支持,注意跟下面parserOptions的ecmaVersion對比一下
// ecmaVersion: 6 表示啓用對於ES6語法的校驗
//
env: {
browser: true,
es6: true,
node: true,
},
//
extends: [
'airbnb',
'airbnb/hooks',
'plugin:react/recommended',
'plugin:unicorn/recommended',
'plugin:promise/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
// 指定解析器
// 默認狀況下,ESLint使用Espree做爲其解析器。您能夠選擇指定在配置文件中使用其餘解析器
parser: '@typescript-eslint/parser',
// parserOptions屬性在文件中設置解析器選項
// ecmaVersion-設置爲三、5(默認),六、七、八、九、10或11,以指定要使用的ECMAScript語法的版本。
// sourceType-設置爲"script"(默認),或者"module"代碼在ECMAScript模塊中。
// ecmaFeatures -一個對象,指示您要使用哪些其餘語言功能,參數以下
// globalReturn-容許return在全局聲明
// impliedStrict-啓用全局嚴格模式(若是ecmaVersion大於等於5)
// jsx-啓用JSX
parserOptions: {
ecmaFeatures: {
impliedStrict: true,
jsx: true,
},
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['react', 'unicorn', 'promise', '@typescript-eslint', 'prettier'],
settings: {
// 這裏的improt/resolver針對插件是eslint-import-resolver-xxx
// 好比下面的typescript裏的規則,針對的就是插件eslint-import-resolver-typescript
// 再下面的node就是配置eslint-import-resolver-node
// 有人說咱們沒依賴eslint-import-resolver-node,哪裏來的呢,是由於
// eslint-import-plugin插件依賴,因此也安裝了它,這就要涉及到npm依賴包平鋪的規則了,下面會講
'import/resolver': {
typescript: {
directory: './tsconfig.json', // 這裏主要解決的是別名的問題,tsconfig.json裏有別名設置
},
node: {
extensions: ['.tsx', '.jsx', '.ts', '.js'],
},
},
},
rules: {
// 如下規則就不詳細講了,由於不少都是由於typescript插件bug跟eslint衝突不得不關閉一些規則
'import/extensions': [
ERROR,
'ignorePackages',
{
ts: 'never',
tsx: 'never',
js: 'never',
},
],
'import/no-extraneous-dependencies': [ERROR, { devDependencies: true }],
'import/prefer-default-export': OFF,
'import/no-unresolved': ERROR,
'import/no-dynamic-require': OFF,
'unicorn/better-regex': ERROR,
'unicorn/prevent-abbreviations': OFF,
'unicorn/filename-case': [
ERROR,
{
cases: {
// 中劃線
kebabCase: true,
// 小駝峯
camelCase: true,
// 下劃線
snakeCase: false,
// 大駝峯
pascalCase: true,
},
},
],
'unicorn/no-array-instanceof': WARN,
'unicorn/no-for-loop': WARN,
'unicorn/prefer-add-event-listener': [
ERROR,
{
excludedPackages: ['koa', 'sax'],
},
],
'unicorn/prefer-query-selector': ERROR,
'unicorn/no-null': OFF,
'unicorn/no-array-reduce': OFF,
'@typescript-eslint/no-useless-constructor': ERROR,
'@typescript-eslint/no-empty-function': WARN,
'@typescript-eslint/no-var-requires': OFF,
'@typescript-eslint/explicit-function-return-type': OFF,
'@typescript-eslint/explicit-module-boundary-types': OFF,
'@typescript-eslint/no-explicit-any': OFF,
'@typescript-eslint/no-use-before-define': ERROR,
'@typescript-eslint/no-unused-vars': WARN,
'no-unused-vars': OFF,
'react/jsx-filename-extension': [ERROR, { extensions: ['.tsx', 'ts', '.jsx', 'js'] }],
'react/jsx-indent-props': [ERROR, 2],
'react/jsx-indent': [ERROR, 2],
'react/jsx-one-expression-per-line': OFF,
'react/destructuring-assignment': OFF,
'react/state-in-constructor': OFF,
'react/jsx-props-no-spreading': OFF,
'react/prop-types': OFF,
'jsx-a11y/click-events-have-key-events': OFF,
'jsx-a11y/no-noninteractive-element-interactions': OFF,
'jsx-a11y/no-static-element-interactions': OFF,
'lines-between-class-members': [ERROR, 'always'],
// indent: [ERROR, 2, { SwitchCase: 1 }],
'linebreak-style': [ERROR, 'unix'],
quotes: [ERROR, 'single'],
semi: [ERROR, 'always'],
'no-unused-expressions': WARN,
'no-plusplus': OFF,
'no-console': OFF,
'class-methods-use-this': ERROR,
'jsx-quotes': [ERROR, 'prefer-single'],
'global-require': OFF,
'no-use-before-define': OFF,
'no-restricted-syntax': OFF,
'no-continue': OFF,
},
};
複製代碼
上面提到一個重要的點,就是eslint-import-resolver-node
,咱們並無在package.json
聲明,咋node_modules裏面就有它了呢?入下
- node_modules
- eslint-import-resolver-node // 爲啥沒有安裝它,它確在第一層
複製代碼
這就涉及到npm安裝依賴包的規則了,由於eslint-import-plugin
依賴eslint-import-resolver-node
,因此,node_modules
裏面就會有,咱們就簡單講一下npm
包安裝(install)規則。
這問題曾經我面試的時候也遇到過,接下來咱們簡單瞭解一下:
咱們都知道,執行 npm install
後,依賴包被安裝到了 node_modules
,在 npm
的早期版本, npm 處理依賴的方式簡單粗暴,以遞歸的形式,嚴格按照 package.json
結構以及子依賴包的 package.json
結構將依賴安裝到他們各自的 node_modules
中。直到有子依賴包不在依賴其餘模塊。也就是說,假如你的package.json以下
{
A模塊:"1.0.0",
B模塊:"1.0.0"
}
複製代碼
而後B模塊有依賴C模塊,B模塊的package.json以下
{
C模塊:"1.0.0"
}
複製代碼
那麼整個項目依賴就是嵌套的,以下:
node_modules
- A模塊
- B模塊
- C模塊
複製代碼
在 Windows 系統中,文件路徑最大長度爲260個字符,嵌套層級過深可能致使不可預知的問題。
爲了解決以上問題,NPM 在 3.x 版本作了一次較大更新。其將早期的嵌套結構改成扁平結構:
安裝模塊時,無論其是直接依賴仍是子依賴的依賴,優先將其安裝在 node_modules
根目錄。
仍是上面的依賴結構,咱們在執行 npm install
後將獲得下面的目錄結構:
node_modules
- A模塊
- B模塊
- C模塊
複製代碼
當安裝到相同模塊時,判斷已安裝的模塊版本是否符合新模塊的版本範圍,若是符合則跳過,不符合則在當前模塊的 node_modules 下安裝該模塊。
就是說假如C模塊依賴A模塊的2.0.0版本,依賴圖以下:
node_modules
- A模塊 1.0.0
- B模塊
- C模塊
- A模塊 2.0.0
複製代碼
其實鋪平的結構也會有問題,咱們這裏就不詳述了,上面提到的那篇文章真的不錯,推薦詳細看,裏面設置npm相關知識的點比這裏談到的多得多。
{
// tab縮進大小,默認爲2
tabWidth: 2,
// 使用tab縮進,默認false
useTabs: true,
// 使用分號, 默認true
semi: false,
// 使用單引號, 默認false(在jsx中配置無效, 默認都是雙引號)
singleQuote: true,
// 行尾逗號,默認none,可選 none|es5|all
// es5 包括es5中的數組、對象
// all 包括函數對象等全部可選
TrailingCooma: "none",
// 對象中的空格 默認true
// true: { foo: bar }
// false: {foo: bar}
bracketSpacing: true,
// JSX標籤閉合位置 默認false
// false: <div
// className=""
// style={{}}
// >
// true: <div
// className=""
// style={{}} >
jsxBracketSameLine:false,
// 箭頭函數參數括號 默認avoid 可選 avoid| always
// avoid 能省略括號的時候就省略 例如x => x
// always 老是有括號
arrowParens: 'always',
}
複製代碼
module.exports = {
// stylelint的配置能夠在已有配置的基礎上進行擴展,以後你本身書寫的配置項將覆蓋已有的配置。
// 配置的含義,咱們前面已經講過簡單提一下stylelint-config-rational-order是配置書寫順序的
extends: ['stylelint-config-standard', 'stylelint-config-rational-order', 'stylelint-config-prettier'],
// plugins通常是由社區提供的,對stylelint已有規則進行擴展
// 也就說有些規則本來stylelint沒有,就要插件自定義規則了
// 'stylelint-declaration-block-no-ignored-properties'這個插件的做用是警告那些不起做用的屬性
plugins: ['stylelint-order', 'stylelint-declaration-block-no-ignored-properties', 'stylelint-scss'],
rules: {
// rules不詳述了,能夠訪問這個網站搜尋,https://stylelint.docschina.org/user-guide/plugins/
'plugin/declaration-block-no-ignored-properties': true,
'comment-empty-line-before': null,
'declaration-empty-line-before': null,
'function-name-case': 'lower',
'no-descending-specificity': null,
'no-invalid-double-slash-comments': null,
'block-no-empty': null,
'value-keyword-case': null,
'rule-empty-line-before': ['always', { except: ['after-single-line-comment', 'first-nested'] }],
'at-rule-no-unknown': null,
'scss/at-rule-no-unknown': true,
},
// 忽略校驗的文件,其中/**/*是glob語法,指的是全部文件和文件夾
ignoreFiles: ['node_modules/**/*', 'build/**/*', 'dist/**/*'],
};
複製代碼
爲何使用 tsconfig.json?
一般咱們可使用 tsc 命令來編譯少許 TypeScript 文件, 但若是實際開發的項目,不多是隻有單個文件,當咱們須要編譯整個項目時,就可使用 tsconfig.json 文件,將須要使用到的配置都寫進 tsconfig.json 文件
{
// 編譯選項,跟編譯ts相關
"compilerOptions": {
// 指定編譯的ECMAScript目標版本。
// 枚舉值:"ES3", "ES5", "ES6"/ "ES2015", "ES2016", "ES2017","ESNext"。
// 默認值: 「ES3」,ESNext包含提案的內容
"target": "ES5",
// 指定生成哪一個模塊系統代碼。枚舉值:"None", "CommonJS", "AMD", "System", "UMD",
// "ES6", "ES2015","ESNext"。默認值根據--target選項不一樣而不一樣,當target設置爲ES6時,
// 默認module爲「ES6」,不然爲「commonjs」
"module": "ESNext",
// 編譯過程當中須要引入的庫文件的列表。好比沒有esnext,Set、Reflect等api會被ts報錯
"lib": ["dom", "dom.iterable", "esnext"],
// 是否容許編譯javascript文件。若是設置爲true,js後綴的文件也會被typescript進行編譯
"allowJs": true,
// 指定 jsx 代碼的生成: 'preserve', 'react-native', or 'react'
"jsx": "react",
// 下面詳解
"isolatedModules": true,
// 用於指定是否啓用嚴格的類型檢查,不過到底具體怎麼嚴格我也不知道
"strict": true,
// 下面詳解
"moduleResolution": "node",
// 下面詳解
"esModuleInterop": true,
"resolveJsonModule": true,
// 下面詳解
"baseUrl": "./",
// 路徑別名,跟webpack alias同樣,注意你是ts的話,必須webpack和ts都配
"paths": {
"Src/*": ["src/*"],
"Components/*": ["src/components/*"],
"Utils/*": ["src/utils/*"]
},
// 如下兩個是跟裝飾器功能有關,experimentalDecorators是 是否開啓裝飾器
// emitDecoratorMetadata是裝飾器裏的一個功能,若是你使用依賴注入,有可能須要開啓它
// 依賴注入不懂的同窗能夠略過,後面會寫一篇關於學習nestjs前置知識的文章
// 會講怎麼使用emitDecoratorMetadata實現依賴注入
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
// 禁止對同一個文件的不一致的引用。主要是文件大小寫必須一致,好比引用a.js和A.js是不同的
"forceConsistentCasingInFileNames": true,
// 忽略全部的聲明文件( `*.d.ts`)的類型檢查
"skipLibCheck": true,
// 下面詳解
"allowSyntheticDefaultImports": true,
// 不生成輸出文件
"noEmit": true
},
"exclude": ["node_modules"]
}
複製代碼
上面的jsx選項能夠有三個值選擇,咱們詳細解釋一下:
jsx可選項包括:preserve, react 和 react-native。
這些模式僅影響編譯階段 - 類型檢查不受影響。
preserve
模式將保持JSX
做爲輸出的一部分,又後面的編譯器繼續編譯(例如Babel)。 此外,輸出將具備.jsx文件擴展名。React.createElement
,在使用以前不須要通過JSX
轉換,輸出將具備.js文件擴展名。JSX
,但輸出將具備.js文件擴展名isolatedModules,這個選項有點複雜,查閱了很多資料。。。下面詳細講一下:
在 TypeScript 中,你能夠引入一個類型,而後再將其導出:
import { someType, someFunction } from "someModule";
someFunction();
export { someType, someFunction };
Try
複製代碼
因爲 someType
並無值,因此生成的 export
將不會導出它(不然將致使 JavaScript 運行時的錯誤):
export { someFunction };
複製代碼
單文件轉譯器並不知道 someType
是否會產生一個值,因此導出一個只指向類型的名稱會是一個錯誤。
若是設置了 isolatedModules
,則全部的實現文件必須是模塊 (也就是它有某種形式的 import
/export
)。若是任意文件不是模塊就會發生錯誤:
function fn() {}
'index.ts' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module.'index.ts' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module.Try
複製代碼
此限制不適用於 .d.ts
文件
const enum
成員在 TypeScript 中,當你引用一個 const enum
的成員時,該引用在生成的 JavaScript 中將會被其實際值所代替。這會將這樣的 TypeScript 代碼:
declare const enum Numbers {
Zero = 0,
One = 1,
}
console.log(Numbers.Zero + Numbers.One);
複製代碼
轉換爲這樣的 JavaScript:
"use strict";
console.log(0 + 1);
複製代碼
在不知道這些成員值的狀況下,其餘轉譯器不能替換對 Numbers
的引用。若是無視的話則會致使運行時錯誤(運行時沒有 Numbers
) 對象。 正因如此,當啓用 isolatedModules
時,引用環境中的 const enum
成員將會是一個錯誤
可選值: classic | node
咱們舉一個例子,看看兩種模式的工做機制,假設用戶主目錄下有一個ts-test的項目,裏面有一個src目錄,src目錄下有一個a.ts文件,即/Users/**/ts-test/src/a.ts
對於相對路徑模塊: 只會在當前相對路徑下查找是否存在該文件(.ts文件),不會做進一步的解析,如"./src/a.ts"文件中,有一行import { b } from "./b"
,那麼其只會檢測是否存在"./src/b.ts"
,沒有就算找不到。
對於非相對路徑模塊: 編譯器則會從包含導入文件的目錄開始依次向上級目錄遍歷,嘗試定位匹配的ts文件或者d.ts類型聲明文件,若是/Users/**/ts-test/src/a.ts
文件中有一行import { b } from "b"
,那麼其查找過程以下:
/Users/**/ts-test/src/b.ts
/Users/**/ts-test/src/b.d.ts
/Users/**/ts-test/b.ts
/Users/**/ts-test/b.d.ts
/Users/**/b.ts
/Users/**/b.d.ts
/Users/b.ts
/Users/b.d.ts
/b.ts
/b.d.ts
複製代碼
/Users/**/ts-test/src/node_modules/b.ts
/Users/**/ts-test/src/node_modules/b.d.ts
/Users/**/ts-test/src/node_modules/b/package.json(若是指定了main)
/Users/**/ts-test/src/node_modules/b/index.ts
/Users/**/ts-test/src/node_modules/b/index.d.ts
/Users/**/ts-test/node_modules/b.ts
/Users/**/ts-test/node_modules/b.d.ts
/Users/**/ts-test/node_modules/b/package.json(若是指定了main)
/Users/**/ts-test/node_modules/index.ts
/Users/**/ts-test/node_modules/index.d.ts
/Users/**/node_modules/b.ts
/Users/**/node_modules/b.d.ts
/Users/**/node_modules/b/package.json(若是指定了main)
/Users/**/node_modules/index.ts
/Users/**/node_modules/index.d.ts
/Users/node_modules/b.ts
/Users/node_modules/b.d.ts
/Users/node_modules/b/package.json(若是指定了main)
/Users/node_modules/index.ts
/Users/node_modules/index.d.ts
/node_modules/b.ts
/node_modules/b.d.ts
/node_modules/b/package.json(若是指定了main)
/node_modules/index.ts
/node_modules/index.d.ts
複製代碼
以上須要注意一點的是,還有一個typeRoots屬性,默認是node_modules/@types,而且無論是classic解析仍是node解析,都會到node_modules/@types目錄下查找類型聲明文件,即typeRoots和模塊的解析規則無關
這個是用於拓寬引入非相對模塊時的查找路徑的。其默認值就是"./" ,好比當moduleResolution
屬性值爲node的時候,若是咱們引入了一個非相對模塊,那麼編譯器只會到node_modules目錄下去查找,可是若是配置了baseUrl
,那麼編譯器在node_modules
中沒有找到的狀況下,還會到baseUrl中指定的目錄下查找;
一樣moduleResolution
屬性值爲classic
的時候也是同樣,除了到當前目錄下找以外(逐層),若是沒有找到還會到baseUrl
中指定的目錄下查找;就是至關於拓寬了非相對模塊的查找路徑範圍
當設置爲 true, 而且模塊沒有顯式指定默認導出時,allowSyntheticDefaultImports
可讓你這樣寫導入:
import React from "react";
複製代碼
而不是:
import * as React from "react";
複製代碼
例如:allowSyntheticDefaultImports
不爲 true 時:
// @filename: utilFunctions.js
Module '"/home/runner/work/TypeScript-Website/TypeScript-Website/packages/typescriptlang-org/utilFunctions"' has no default export.Module '"/home/runner/work/TypeScript-Website/TypeScript-Website/packages/typescriptlang-org/utilFunctions"' has no default export.
const getStringLength = (str) => str.length;
module.exports = {
getStringLength,
};
// @filename: index.ts
import utils from "./utilFunctions";
const count = utils.getStringLength("Check JS");
複製代碼
這段代碼會引起一個錯誤,由於沒有「default」對象能夠導入,即便你認爲應該有。 爲了使用方便,Babel 這樣的轉譯器會在沒有默認導出時自動爲其建立,使模塊看起來更像:
// @filename: utilFunctions.js
const getStringLength = (str) => str.length;
const allFunctions = {
getStringLength,
};
module.exports = allFunctions;
module.exports.default = allFunctions;
複製代碼
本選項不會影響 TypeScript 生成的 JavaScript,它僅對類型檢查起做用。
這個參數涉及到es6模塊和commonjs模塊互相轉換知識點了。具體參考這篇文章(這一參數就是一篇文章 esModuleInterop 到底作了什麼?, 我這裏簡引用一下這篇文章的關鍵點。
首先咱們看一下import
語法在ts中是如何被轉譯的!
TS 對於 import 變量的轉譯規則爲:
// before
import React from 'react';
console.log(React)
// after
var React = require('react');
console.log(React['default'])
// before
import {Component} from 'react';
console.log(Component);
// after
var React = require('react');
console.log(React.Component)
// before
import * as React from 'react';
console.log(React);
// after
var React = require('react');
console.log(React);
複製代碼
結論,能夠看到:
import
導入默認導出的模塊,TS
在讀這個模塊的時候會去讀取上面的 default
屬性import
導入非默認導出的變量,TS
會去讀這個模塊上面對應的屬性import *,TS
會直接讀該模塊TS、babel
對 export` 變量的轉譯規則爲:(代碼通過簡化)
// before
export const name = "esm";
export default {
name: "esm default",
};
// after
exports.__esModule = true;
exports.name = "esm";
exports["default"] = {
name: "esm default"
}
複製代碼
能夠看到:
export default
的變量,TS
會將其放在 module.exports
的 default 屬性上export
的變量,TS
會將其放在 module.exports
對應變量名的屬性上module.exports
增長一個 __esModule: true 的屬性,用來告訴編譯器,這原本是一個 esm 模塊
回到標題上,esModuleInterop
這個屬性默認爲 false。改爲 true 以後,TS 對於 import 的轉譯規則會發生一些變化(export 的規則不會變):
// before
import React from 'react';
console.log(React);
// after 代碼通過簡化
// __importDefault規則以下:
// 若是目標模塊是 esm,就直接返回目標模塊;不然將目標模塊掛在一個對象的 defalut 上,返回該對象
var react = __importDefault(require('react'));
console.log(react['default']);
// before
import {Component} from 'react';
console.log(Component);
// after 代碼通過簡化
var react = require('react');
console.log(react.Component);
// before
import * as React from 'react';
console.log(React);
// after 代碼通過簡化
// _importStar 規則以下
// 若是目標模塊是 esm,就直接返回目標模塊。不然
// 將目標模塊上全部的除了 default 之外的屬性挪到 result 上
// 將目標模塊本身掛到 result.default 上
var react = _importStar(require('react'));
console.log(react);
複製代碼
能夠看到,對於默認導入和 namespace(*)導入,TS 使用了兩個 helper 函數來幫忙
// 代碼通過簡化
var __importDefault = function (mod) {
return mod && mod.__esModule ? mod : { default: mod };
};
var __importStar = function (mod) {
if (mod && mod.__esModule) {
return mod;
}
var result = {};
for (var k in mod) {
if (k !== "default" && mod.hasOwnProperty(k)) {
result[k] = mod[k]
}
}
result["default"] = mod;
return result;
};
複製代碼
其實這個參數對於咱們項目而言沒有用,由於@babel/preset-typescript
會把類型清除掉,webpack
不會調用 tsc
,tsconfig.json
也會被忽略掉。
可是能夠幫助咱們拓寬視野,這樣面試官讓你聊es6
模塊和commonjs
模塊轉換的話題(cjs 導入 esm (通常不會這樣使用,除開這種狀況),就會遊刃有餘
首先是工具文件:
// 判讀是不是生產環境,這裏這個項目的做者取了一個巧,判斷非develop環境是這樣的
// process.env.NODE_ENV !== 'production'
// 這樣寫不要好,有可能大家公司有不少環境,好比還有預發、灰度環境等等
const isDevelopment = process.env.NODE_ENV !== 'production';
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
isDevelopment,
isProduction,
};
複製代碼
// 如下是兩個node模塊
const path = require('path');
const fs = require('fs');
// 同步獲取node執行的文件的工做目錄, 咱們的工做目錄通常都是項目的根目錄,這裏就表示根目錄
// 爲啥這麼說呢,由於package.json寫着webpack --config ./scripts/config/webpack.prod.js
// webpack就是藉助node的能力,它的 ./scripts就暴露是以項目目錄爲根目錄
// 這裏須要注意process.cwd和__dirname的區別
// process.cwd()返回當前工做目錄。如:調用node命令執行腳本時的目錄。
// __dirname返回源代碼所在的目錄
const appDirectory = fs.realpathSync(process.cwd());
// 獲取絕對路徑的方法函數
function resolveApp(relativePath) {
return path.resolve(appDirectory, relativePath);
}
// 默認extentions
const moduleFileExtensions = ['ts', 'tsx', 'js', 'jsx'];
/** * Resolve module path * @param {function} resolveFn resolve function * @param {string} filePath file path */
function resolveModule(resolveFn, filePath) {
// Check if the file exists
const extension = moduleFileExtensions.find((ex) => fs.existsSync(resolveFn(`${filePath}.${ex}`)));
if (extension) {
return resolveFn(`${filePath}.${extension}`);
}
return resolveFn(`${filePath}.ts`); // default is .ts
}
module.exports = {
appBuild: resolveApp('build'),
appPublic: resolveApp('public'),
appIndex: resolveModule(resolveApp, 'src/index'), // Package entry path
appHtml: resolveApp('public/index.html'),
appNodeModules: resolveApp('node_modules'), // node_modules path
appSrc: resolveApp('src'),
appSrcComponents: resolveApp('src/components'),
appSrcUtils: resolveApp('src/utils'),
appProxySetup: resolveModule(resolveApp, 'src/setProxy'),
appPackageJson: resolveApp('package.json'),
appTsConfig: resolveApp('tsconfig.json'),
moduleFileExtensions,
};
複製代碼
這是webpack
生產環境和開發環境共同的配置文件 如下須要特別注意的參數是'css-loader'裏有個importLoaders的參數,它的意思是須要舉一個例子就明白了,
以下圖:importLoader是1
咱們在寫sass
或者less
的時候能夠@import
去引入其餘的sass
或less
文件,此時引用的文件如何被loader處理就跟這個參數有關了。
當css-loader
處理index.scss
文件,讀取到@import
語句的時候, 由於將importLoaders
設置爲1
,那麼a.scss
和b.scss
會被postcss-loader
給處理
若是將importLoaders
設置爲2
,那麼 a.scss
和b.scss
就會被postcss-loader
和sass-loader
給處理
下面的externals
屬性是一個常見webpack
優化點,好比你會把react,react-dom
放入cdn
,這樣就不用打包他們
這裏還有一些webpack5
和webpack4
相同功能但配置有些區別的點:
file-loader
,可是 webpack5
如今已默認內置資源模塊,根據官方配置,如今能夠改成如下配置方式,再也不須要安裝額外插件:module.exports = {
output: {
// ...
assetModuleFilename: 'images/[name].[contenthash:8].[ext]',
},
// other...
module: {
rules: [
// other...
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024,
},
},
},
{
test: /\.(eot|svg|ttf|woff|woff2?)$/,
type: 'asset/resource',
},
]
},
plugins: [//...],
}
複製代碼
這裏提一個醒dll
在webpack
裏已通過時了!過期了!之後誰給你推薦這個webpack優化就別理他就好了!由於配置hard-source-webpack-plugin都比配置dll容易的多,這仍是webpack4的配置。都過期了
以前可使用插件 hard-source-webpack-plugin
實現緩存,大大加快二次編譯速度,如今webpack5
如今默認支持緩存,咱們只須要如下配置便可:
module.exports = {
//...
cache: {
// 默認type是memory也就是緩存放到內存中
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
//...
};
複製代碼
cache.buildDependencies
,它能夠指定構建過程當中的代碼依賴。它的值類型有兩種:文件和目錄。
以下示例的意思是: __filename
變量指向 node.js
中的當前文件。
cache.buildDependencies: {
// 它的做用是當配置文件內容或配置文件依賴的模塊文件發生變化時,當前的構建緩存即失效
config: [__filename]
}
複製代碼
注意:當設置 cache.type: "filesystem"
時,webpack
會在內部以分層方式啓用文件系統緩存和內存緩存。 從緩存讀取時,會先查看內存緩存,若是內存緩存未找到,則降級到文件系統緩存。 寫入緩存將同時寫入內存緩存和文件系統緩存。也就是說它比memory
模式更好
// 插件把 webpack 打包後的靜態文件自動插入到 html 文件當中
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 用來分離css爲單獨的文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 添加打包進度條插件
const WebpackBar = require('webpackbar');
// 它在一個單獨的進程上運行類型檢查器,該插件在編譯之間重用抽象語法樹,並與TSLint共享這些樹。
// 能夠經過多進程模式進行擴展,以利用最大的CPU能力。
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
// 在webpack中拷貝文件和文件夾
const CopyPlugin = require('copy-webpack-plugin');
// 引入路徑工具,上文已講
const paths = require('../paths');
// 引入環境判斷工具,上文已講
const { isDevelopment, isProduction } = require('../env');
// 引入配置文件,上文已講
const { imageInlineSizeLimit } = require('../conf');
// 這個函數是用來加載css相關loader的函數
// 若是是開發環境用style-loader,將css內嵌到html中,反之css單獨打包
const getCssLoaders = (importLoaders) => [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: false,
sourceMap: isDevelopment,
importLoaders,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('postcss-flexbugs-fixes'),
isProduction && [ // 開發環境不使用postcss-preset-env加瀏覽器前綴,加快打包時間
'postcss-preset-env',
{
autoprefixer: {
grid: true,
flexbox: 'no-2009',
},
stage: 3,
},
],
].filter(Boolean),
},
},
},
];
module.exports = {
// 入口信息
entry: {
app: paths.appIndex,
},
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
// 這裏能夠設置extensions和別名
// extensions就是webpack會識別的文件後綴的順序,
// 若是你是tsx建議放到第一位,不然你寫成['ts','tsx']會先檢測是不是ts文件,不是才接着看是否是tsx
resolve: {
extensions: ['.tsx', '.ts', '.js', '.json'],
alias: {
Src: paths.appSrc,
Components: paths.appSrcComponents,
Utils: paths.appSrcUtils,
},
},
externals: {
react: 'React',
'react-dom': 'ReactDOM',
axios: 'axios',
},
module: {
rules: [
{
test: /\.(tsx?|js)$/,
loader: 'babel-loader',
options: { cacheDirectory: true }, // 這是一個webpack優化點,使用緩存
exclude: /node_modules/, // 這個也是webpack優化的點 exclude排除不須要編譯的文件夾
},
{
test: /\.css$/,
use: getCssLoaders(1), // 這個講得就是importLoaders屬性運用,上面已經講了
},
{
test: /\.scss$/,
use: [
...getCssLoaders(2),
{
loader: 'sass-loader',
options: {
sourceMap: isDevelopment,
},
},
],
},
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
type: 'asset', // webpack5自帶的loader,webpack4依賴file-loader
parser: {
dataUrlCondition: {
maxSize: imageInlineSizeLimit,
},
},
},
{
test: /\.(eot|svg|ttf|woff|woff2?)$/,
type: 'asset/resource', // webpack5自帶的loader,webpack4依賴file-loader
},
],
},
plugins: [
new HtmlWebpackPlugin({ // 這個模塊是重點,下面詳細講
template: paths.appHtml,
cache: true,
}),
new CopyPlugin({ // 這個是複製文件或者目錄的插件
patterns: [
{
context: paths.appPublic,
from: '*',
to: paths.appBuild,
toType: 'dir',
globOptions: {
dot: true,
gitignore: true,
ignore: ['**/index.html'],
},
},
],
}),
// 打包進度條插件
new WebpackBar({
name: isDevelopment ? 'RUNNING' : 'BUNDLING',
color: isDevelopment ? '#52c41a' : '#722ed1',
}),
// 插件功能上面已寫
new ForkTsCheckerWebpackPlugin({
typescript: {
configFile: paths.appTsConfig,
},
}),
],
};
複製代碼
HtmlWebpackPlugin
生成html文件的標題
就是html文件的文件名,默認是index.html
指定你生成的文件所依賴哪個html文件模板,模板類型能夠是html、ejs
若是你設置的 title
和 filename
於模板中發生了衝突,那麼以你的title
和 filename
的配置值爲準。
inject**
inject有四個值: true
body
head
false
true
默認值,script標籤位於html文件的 body 底部body
script標籤位於html文件的 body 底部head
script標籤位於html文件的 head中false
不插入生成的js文件,這個幾乎不會用到的favicon
給你生成的html文件生成一個 favicon
,值是一個路徑
plugins: [
new HtmlWebpackPlugin({
...
favicon: 'path/to/my_favicon.ico'
})
複製代碼
而後再生成的html中就有了一個 link
標籤
使用minify會對生成的html文件進行壓縮。注意,不能直接這樣寫:minify: true
, 使用時候必須給定一個 { }
對象 )
plugins: [
new HtmlWebpackPlugin({
...
minify: {
removeAttributeQuotes: true // 移除屬性的引號
}
})
]
複製代碼
chunks主要用於多入口文件,當你有多個入口文件,那就回編譯後生成多個打包後的文件,那麼chunks
就能選擇你要使用那些js文件
entry: {
index: path.resolve(__dirname, './src/index.js'),
devor: path.resolve(__dirname, './src/devor.js'),
main: path.resolve(__dirname, './src/main.js')
}
plugins: [
new httpWebpackPlugin({
chunks: ['index','main']
})
]
複製代碼
那麼編譯後:
<script type=text/javascript src="index.js"></script>
<script type=text/javascript src="main.js"></script>
複製代碼
若是你沒有設置chunks選項,那麼默認是所有顯示
chunksSortMode
script的順序,默認四個選項: none
auto
dependency
{function}
'dependency' 不用說,按照不一樣文件的依賴關係來排序。
這裏重點講解一下function
的用法
如何配置出咱們想要的順序
new HtmlWebpackPlugin({
...
chunksSortMode: function (chunk1, chunk2) {
var order = ['common', 'public', 'index'];
var order1 = order.indexOf(chunk1.names[0]);
var order2 = order.indexOf(chunk2.names[0]);
return order1 - order2;
}
})
複製代碼
以上配置的順序就是['common', 'public', 'index'],爲何呢,由於chunksSortMode這個函數就是數組的sort方法裏的自定義函數,這裏說白了就是數組[0, 1, 2]按升序排列。
接下來還有webpack.dev.js和webpack.prod.js兩個文件(有點寫不下去了,這篇文章查了n多資料,搞得如今腦殼有點昏啊)
我就快速寫重點內容了,不貼代碼了
webpack.dev.js裏面的重點是devServer屬性的配置
devServer配置詳解:
devServer: {
// 提供靜態文件目錄地址
// 基於express.static實現, 因此這裏你若是不請求靜態文件,這個屬性沒啥用
contentBase: path.join(__dirname, 'dist'),
// 任意的 404 響應都被替代爲 index.html
// 基於node connect-history-api-fallback包實現
// 咱們知道vue和react有hash路由和history路由
// history路由須要設置這個參數爲true,要不你刷新頁面會空白屏
historyApiFallback: true,
// 是否一切服務都啓用 gzip 壓縮
// 基於node compression包實現
compress: true,
// 是否隱藏bundle信息
noInfo: true,
// 發生錯誤是否覆蓋在頁面上
overlay: true,
// 是否開啓熱加載
// 必須搭配webpack.HotModuleReplacementPlugin 才能徹底啓用 HMR。
// 若是 webpack 或 webpack-dev-server 是經過 --hot 選項啓動的,那麼這個插件會被自動添加
hot: true,
// 熱加載模式
// true表明inline模式,false表明iframe模式
inline: true, // 默認是true
// 是否自動打開
open: true,
// 設置本地url和端口號
host: 'localhost',
port: 8080,
// 代理
// 基於node http-proxy-middleware包實現
proxy: {
// 匹配api前綴時,則代理到3001端口
// 即http://localhost:8080/api/123 = http://localhost:3001/api/123
// 注意:這裏是把當前server8080代理到3001,而不是任意端口的api代理到3001
'/api': 'http://localhost:3001',
// 設置爲true, 本地就會虛擬一個服務器接收你的請求並代你發送該請求
// 主要解決跨域問題
changeOrigin: true,
// 針對代理https
secure: false,
// 覆寫路徑:http://localhost:8080/api/123 = http://localhost:3001/123
pathRewrite: {'^/api' : ''}
}
}
複製代碼
// 這個插件最開始講了,一下的插件就略過都講過了
const { merge } = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const common = require('./webpack.common.js');
const paths = require('../paths');
const { shouldOpenAnalyzer, ANALYZER_HOST, ANALYZER_PORT } = require('../conf');
module.exports = merge(common, {
mode: 'production', 這個須要細講,下面說
output: {
filename: 'js/[name].[contenthash:8].js',
path: paths.appBuild,
assetModuleFilename: 'images/[name].[contenthash:8].[ext]',
},
plugins: [
// 打包後會有dist(或者build,名字在output裏設置)目錄
// 再次打包時須要把以前的dist刪掉後,再次生成dist
// 這個插件就是其刪掉做用的
new CleanWebpackPlugin(),
// 提取css的插件
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css',
}),
// 開啓分析工具的插件,分析包的體積
shouldOpenAnalyzer &&
new BundleAnalyzerPlugin({
analyzerMode: 'server',
analyzerHost: ANALYZER_HOST,
analyzerPort: ANALYZER_PORT,
}),
].filter(Boolean),
// 這個重點下面講
optimization: {
concatenateModules: false,
minimize: true,
minimizer: [
// new TerserPlugin({ // 這個經常使用配置後面下面講
// extractComments: false,
// terserOptions: {
// compress: { pure_funcs: ['console.log'] },
// },
// }),
new CssMinimizerPlugin(), // css壓縮插件
],
splitChunks: { // 這個是重點下面講
chunks: 'all',
minSize: 0,
},
},
});
複製代碼
TerserPlugin
默認值:/.m?js(?.*)?$/i
, 用來匹配須要壓縮的文件。
默認值: undefined
, 匹配參與壓縮的文件
默認值: undefined
, 匹配參與壓縮的文件
parallel
類型: Boolean|Number
默認值: true
這個參數很重要,啓用多進程構建,能夠大大提升打包速度,強烈建議開啓
terserOptions: {
format: {
// 刪除全部的註釋
comments: true,
}
compress: {
// 刪除未引用的函數和變量
unused: true,
// 刪掉 debugger
drop_debugger: true,
// 移除 console
drop_console: true,
// 刪除沒法訪問的代碼
dead_code: true,
unsafe_undefined: true,
}
}
複製代碼
mode
mode有三個參數production
,development
,none
,前兩個參數會默認安裝一堆插件,用來區分是開發環境仍是生產環境。而none
的話,webpack就是最初的樣子,無任何預設,須要從無到有開始配置。
因此咱們瞭解是哪些插件,有啥用是理解webpack進化到如今的比較重要的知識點。
在此mode下,就作了如下插件的事(還有其它配置,重點介紹下面的),其餘都沒作,因此這些插件能夠省略,webpack默認就給你加上了,並且會將 DefinePlugin
中 process.env.NODE_ENV
的值設置爲 development
// webpack.development.config.js
module.exports = {
+ mode: 'development'
- devtool: 'eval',
optimization: {
moduleIds: 'named',
chunkIds: 'named'
}
}
複製代碼
咱們看看moduleIds
和chunkIds
這兩個配置都作了啥,簡而言之,就是幫助緩存生效的插件。
咱們知道webpack最開始的版本並不會給模塊加上名字,模塊名都是數字,0,1,2,3
,可是對於咱們人來講數字很差認,要是名字多好,便於開發的時候查找。
並且,你想一想,若是咱們在0
和1
模塊之間,再加一個模塊,那麼順序就是0
、新模塊(如今是1
)、老的1模塊(如今是2
),老的2
模塊(如今是3
),這時候新模塊就是1
,其它老模塊數字依次+1
,這個時候緩存就失效了,雖然老的模塊代碼沒變,可是這種緩存下標的方式,讓緩存很容易失效,這就是爲啥加上這個配置的緣由
有了moduleIds
,模塊都擁有了姓名,並且都是獨一無二的key,無論新增減多少模塊,模塊的key都是固定的。
除了moduleIds
,還有一個chunkIds
,這個是給配置的每一個chunks命名,本來的chunks也是數組,沒有姓名。
在正式版本中,所省略的插件們,以下所示,咱們會一個個分析。
// webpack.production.config.js
module.exports = {
+ mode: 'production'
- plugins: [
- new webpack.DefinePlugin({ "process.env.NODE_ENV": '"production"' }),
- new webpack.optimize.ModuleConcatenationPlugin(),
- new webpack.NoEmitOnErrorsPlugin(),
- new TerserPlugin(/* ... */),
- ]
}
複製代碼
用於js代碼壓縮。在之前版本中,咱們須要引入npm包terser-webpack-plugin
來進行壓縮,如今咱們能夠在optimize
中進行配置達到一樣的效果
配置以前已講
這個是用來幫助做用域提高的,咱們以前看了webpack打包出來的是相似
{
文件路徑1:function()xx, 文件路徑2:function()xx, 文件路徑3:function()xx, } 複製代碼
這樣每一個模塊都在本身的function裏面,都有本身的做用域,咱們知道做用域鏈訪問是有性能代價的,若是你們都提到一個做用域,對性能提高是有幫助的,這個插件就作這樣的事。
這個就是用於防止程序報錯,就算有錯誤也給我繼續編譯。
還有一些默認的插件配置,也就是能夠不在plugins中引用的配置:
webpack.optimization.sideEffects
用於實現treeshaking
形式的死碼刪除。而爲了實現treeshaking
,須要知足幾個條件:
這樣,通過SideEffectsFlagPlugin
處理後,沒有反作用且沒有被使用的模塊都會被打上sideEffectFree
標記。 在ModuleConcatenationPlugin
中,帶着sideEffectFree
標記的模塊將不會被打包。
// webpack.pord.config.js
module.exports = {
optimization: {
sideEffects: true
}
};
複製代碼
即配置optimization.flagIncludedChunks
。該配置項會使webpack確認,若當前標記的chunk
a是另一個chunk
A的子集而且已經A加載完成,則a將不會再次加載(包含關係)。
// webpack.pord.config.js
module.exports = {
optimization: {
flagIncludedChunks: true
}
};
複製代碼
標記沒有用到的依賴。
最後1個知識點來了哦!
這個配置對象中,其它都好說,最使人困惑的是chunks屬性,咱們來看看是個什麼東西。
chunks
選項,決定要提取那些模塊。
默認是async
:只提取異步加載的模塊出來打包到一個文件中。
import('xxx')
或require(['xxx'],() =>{})
加載的模塊。initial
:提取同步加載和異步加載模塊,若是xxx在項目中異步加載了,也同步加載了,那麼xxx這個模塊會被提取兩次,分別打包到不一樣的文件中。
import xxx
或require('xxx')
加載的模塊。all
:無論異步加載仍是同步加載的模塊都提取出來,打包到一個文件中。
兄弟們,可是我遇到了問題,就是上面說的這些根本無論用,下面的案例摘自stockOverFolw
的高票回答,可是我用webpack5
一樣的配置,根本得不到跟這個回答一致的答案,百思不得其解,後面我改進了一下,就能夠了,後面再介紹,你們先看案例
app.js 以下,有一個靜態模塊導入叫my-static-module
,還有一個動態模塊導入叫my-dynamic-module
//app.js
import "my-static-module";
if(some_condition_is_true){
import ("my-dynamic-module")
}
console.log("My app is running")
複製代碼
``
來看看chunks參數不同,獲得的結果會是多麼不同(配置以下)
module.exports = {
optimization: {
chunks: async | initial | all
}
};
複製代碼
會生成如下兩個文件
bundle.js
(包括 app.js + my-static-module)chunk.js
(僅僅包括 my-dynamic-module)會生成如下兩個文件
app.js
(僅僅包括 app.js)bundle.js
(僅僅包括 my-static-module)chunk.js
(僅僅包括 my-dynamic-module)會生成如下兩個文件
app.js
(僅僅包括 app.js only)bundle.js
(僅僅包括 my-static-module + my-dynamic-module)能夠看出,all
是比較極限的壓縮
我不管怎麼嘗試,得出來的結果都是默認的async
導出的結果,多是我配錯了吧,但願有熟悉這項配置的大哥評論區留個言。
我後來是怎麼改,就能夠符合上面的答案了呢,我把chunks配置在cacheGroups
參數裏,以下:
module.exports = {
splitChunks: {
cacheGroups: {
common: {
chunks: 'async' | 'all' | 'initial',
minSize: 0,
minChunks: 1,
},
},
},
};
複製代碼
這裏順便介紹一下minChunks是什麼意思,意思是至少引用多少次才分離公共代碼,我這裏是1次,只要引用過模塊都分離出去。
minSize
是規定被提取的模塊在壓縮前的大小最小值,單位爲字節,默認爲30000,只有超過了30000字節纔會被提取,咱們這裏設置爲0,是爲了本身作實驗,保證能被分離就分離出去。
接下來,介紹一下其餘參數:
maxSize
選項:把提取出來的模塊打包生成的文件大小不能超過maxSize值,若是超過了,要對其進行分割並打包生成新的文件。單位爲字節,默認爲0,表示不限制大小。maxAsyncRequests
選項:最大的按需(異步)加載次數,默認爲 6。maxInitialRequests
選項:打包後的入口文件加載時,還能同時加載js文件的數量(包括入口文件),默認爲4。maxInitialRequests
/ maxAsyncRequests
<maxSize
<minSize
。automaticNameDelimiter
選項:打包生成的js文件名的分割符,默認爲~
。name
選項:打包生成js文件的名稱。cacheGroups
選項,核心重點,配置提取模塊的方案。裏面每一項表明一個提取模塊的方案。下面是cacheGroups
每項中特有的選項,其他選項和外面一致,若cacheGroups
每項中有,就按配置的,沒有就使用外面配置的。test
選項:用來匹配要提取的模塊的資源路徑或名稱。值是正則或函數。priority
選項:方案的優先級,值越大表示提取模塊時優先採用此方案。默認值爲0。reuseExistingChunk
選項:true
/false
。爲true
時,若是當前要提取的模塊,在已經在打包生成的js文件中存在,則將重用該模塊,而不是把當前要提取的模塊打包生成新的js文件。enforce
選項:true
/false
。爲true
時,忽略minSize
,minChunks
,maxAsyncRequests
和maxInitialRequests
外面選項能看到最後必定很不容易,歡迎點贊,後面會接着出文章,目前3篇正在寫,也是本身最近學習完的知識
參考: